From 57af278afcfc76c71acf9bdd1d6254b61ab75902 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 25 May 2022 14:37:56 -0400 Subject: [PATCH 01/66] test adding function --- pyart/retrieve/__init__.py | 2 +- pyart/retrieve/echo_class.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyart/retrieve/__init__.py b/pyart/retrieve/__init__.py index 451f01d34c..362fba52c7 100644 --- a/pyart/retrieve/__init__.py +++ b/pyart/retrieve/__init__.py @@ -4,7 +4,7 @@ """ from .kdp_proc import kdp_maesaka, kdp_schneebeli, kdp_vulpiani -from .echo_class import steiner_conv_strat, hydroclass_semisupervised +from .echo_class import steiner_conv_strat, hydroclass_semisupervised, yuter_conv_strat from .echo_class import get_freq_band from .gate_id import map_profile_to_gates, fetch_radar_time_profile from .simple_moment_calculations import calculate_snr_from_reflectivity diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 597a71c3f4..b515ad4885 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -451,3 +451,6 @@ def get_freq_band(freq): warn('Unknown frequency band') return None + +def yuter_conv_strat(): + print('Hello World!') From 5534da880d2373e998ffeefc45ab14533bddb756 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 25 May 2022 14:38:02 -0400 Subject: [PATCH 02/66] Create _echo_class_updated.py --- pyart/retrieve/_echo_class_updated.py | 216 ++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 pyart/retrieve/_echo_class_updated.py diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py new file mode 100644 index 0000000000..826d340e89 --- /dev/null +++ b/pyart/retrieve/_echo_class_updated.py @@ -0,0 +1,216 @@ +import numpy as np + + +def _steiner_conv_strat(refl, x, y, dx, dy, intense=42, peak_relation=0, + area_relation=1, bkg_rad=11000, use_intense=True): + """ + We perform the Steiner et al. (1995) algorithm for echo classification + using only the reflectivity field in order to classify each grid point + as either convective, stratiform or undefined. Grid points are + classified as follows, + + 0 = Undefined + 1 = Stratiform + 2 = Convective + """ + def convective_radius(ze_bkg, area_relation): + """ + Given a mean background reflectivity value, we determine via a step + function what the corresponding convective radius would be. + + Higher background reflectivitives are expected to have larger + convective influence on surrounding areas, so a larger convective + radius would be prescribed. + """ + if area_relation == 0: + if ze_bkg < 30: + conv_rad = 1000. + elif (ze_bkg >= 30) & (ze_bkg < 35.): + conv_rad = 2000. + elif (ze_bkg >= 35.) & (ze_bkg < 40.): + conv_rad = 3000. + elif (ze_bkg >= 40.) & (ze_bkg < 45.): + conv_rad = 4000. + else: + conv_rad = 5000. + + if area_relation == 1: + if ze_bkg < 25: + conv_rad = 1000. + elif (ze_bkg >= 25) & (ze_bkg < 30.): + conv_rad = 2000. + elif (ze_bkg >= 30.) & (ze_bkg < 35.): + conv_rad = 3000. + elif (ze_bkg >= 35.) & (ze_bkg < 40.): + conv_rad = 4000. + else: + conv_rad = 5000. + + if area_relation == 2: + if ze_bkg < 20: + conv_rad = 1000. + elif (ze_bkg >= 20) & (ze_bkg < 25.): + conv_rad = 2000. + elif (ze_bkg >= 25.) & (ze_bkg < 30.): + conv_rad = 3000. + elif (ze_bkg >= 30.) & (ze_bkg < 35.): + conv_rad = 4000. + else: + conv_rad = 5000. + + if area_relation == 3: + if ze_bkg < 40: + conv_rad = 0. + elif (ze_bkg >= 40) & (ze_bkg < 45.): + conv_rad = 1000. + elif (ze_bkg >= 45.) & (ze_bkg < 50.): + conv_rad = 2000. + elif (ze_bkg >= 50.) & (ze_bkg < 55.): + conv_rad = 6000. + else: + conv_rad = 8000. + + return conv_rad + + def peakedness(ze_bkg, peak_relation): + """ + Given a background reflectivity value, we determine what the necessary + peakedness (or difference) has to be between a grid point's + reflectivity and the background reflectivity in order for that grid + point to be labeled convective. + """ + if peak_relation == 0: + if ze_bkg < 0.: + peak = 10. + elif (ze_bkg >= 0.) and (ze_bkg < 42.43): + peak = 10. - ze_bkg ** 2 / 180. + else: + peak = 0. + + elif peak_relation == 1: + if ze_bkg < 0.: + peak = 14. + elif (ze_bkg >= 0.) and (ze_bkg < 42.43): + peak = 14. - ze_bkg ** 2 / 180. + else: + peak = 4. + + return peak + + sclass = np.zeros(refl.shape, dtype=int) + ny, nx = refl.shape + + for i in range(0, nx): + # Get stencil of x grid points within the background radius + imin = np.max(np.array([1, (i - bkg_rad / dx)], dtype=int)) + imax = np.min(np.array([nx, (i + bkg_rad / dx)], dtype=int)) + + for j in range(0, ny): + # First make sure that the current grid point has not already been + # classified. This can happen when grid points within the + # convective radius of a previous grid point have also been + # classified. + if ~np.isnan(refl[j, i]) & (sclass[j, i] == 0): + # Get stencil of y grid points within the background radius + jmin = np.max(np.array([1, (j - bkg_rad / dy)], dtype=int)) + jmax = np.min(np.array([ny, (j + bkg_rad / dy)], dtype=int)) + + n = 0 + sum_ze = 0 + + # Calculate the mean background reflectivity for the current + # grid point, which will be used to determine the convective + # radius and the required peakedness. + + for l in range(imin, imax): + for m in range(jmin, jmax): + if not np.isnan(refl[m, l]): + rad = np.sqrt( + (x[l] - x[i]) ** 2 + (y[m] - y[j]) ** 2) + + # The mean background reflectivity will first be + # computed in linear units, i.e. mm^6/m^3, then + # converted to decibel units. + if rad <= bkg_rad: + n += 1 + sum_ze += 10. ** (refl[m, l] / 10.) + + if n == 0: + ze_bkg = np.inf + else: + ze_bkg = 10.0 * np.log10(sum_ze / n) + + # Now get the corresponding convective radius knowing the mean + # background reflectivity. + conv_rad = convective_radius(ze_bkg, area_relation) + + # Now we want to investigate the points surrounding the current + # grid point that are within the convective radius, and whether + # they too are convective, stratiform or undefined. + + # Get stencil of x and y grid points within the convective + # radius. + lmin = np.max( + np.array([1, int(i - conv_rad / dx)], dtype=int)) + lmax = np.min( + np.array([nx, int(i + conv_rad / dx)], dtype=int)) + mmin = np.max( + np.array([1, int(j - conv_rad / dy)], dtype=int)) + mmax = np.min( + np.array([ny, int(j + conv_rad / dy)], dtype=int)) + + if use_intense and (refl[j, i] >= intense): + sclass[j, i] = 2 + + for l in range(lmin, lmax): + for m in range(mmin, mmax): + if not np.isnan(refl[m, l]): + rad = np.sqrt( + (x[l] - x[i]) ** 2 + + (y[m] - y[j]) ** 2) + + if rad <= conv_rad: + sclass[m, l] = 2 + + else: + peak = peakedness(ze_bkg, peak_relation) + + if refl[j, i] - ze_bkg >= peak: + sclass[j, i] = 2 + + for l in range(imin, imax): + for m in range(jmin, jmax): + if not np.isnan(refl[m, l]): + rad = np.sqrt( + (x[l] - x[i]) ** 2 + + (y[m] - y[j]) ** 2) + + if rad <= conv_rad: + sclass[m, l] = 2 + + else: + # If by now the current grid point has not been + # classified as convective by either the intensity + # criteria or the peakedness criteria, then it must be + # stratiform. + sclass[j, i] = 1 + + return sclass + + +def steiner_class_buff(ze, x, y, z, dx, dy, bkg_rad, + work_level, intense, peak_relation, + area_relation, use_intense): + + zslice = np.argmin(np.abs(z - work_level)) + refl = ze[zslice, :, :] + + area_rel = {"small": 0, "medium": 1, "large": 2, "sgp": 3} + peak_rel = {"default": 0, "sgp": 1} + + sclass = _steiner_conv_strat(refl, x, y, dx, dy, intense=intense, + peak_relation=peak_rel[peak_relation], + area_relation=area_rel[area_relation], + bkg_rad=11000, use_intense=True) + + return sclass From 3dd35fbf3d4fd4c306443960eabd1ed3f9220c47 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 14 Sep 2022 15:06:42 -0400 Subject: [PATCH 03/66] Renaming refl to ref --- examples/plotting/plot_rhi_sigmet.py | 2 +- pyart/correct/attenuation.py | 8 +- pyart/default_config.py | 14 +-- pyart/io/tests/test_mdv_grid.py | 4 +- pyart/tests/custom_config.py | 4 +- test.py | 181 +++++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 test.py diff --git a/examples/plotting/plot_rhi_sigmet.py b/examples/plotting/plot_rhi_sigmet.py index 7b692f5c80..dd9eb1495c 100644 --- a/examples/plotting/plot_rhi_sigmet.py +++ b/examples/plotting/plot_rhi_sigmet.py @@ -39,7 +39,7 @@ display.set_limits(ylim=[0, 17]) cax = fig.add_axes([.9, .1, 0.02, .8]) -colorbar_label = 'Eq refl fact (dBz)' +colorbar_label = 'Eq ref fact (dBz)' display.plot_colorbar(fig=fig, cax=cax, label=colorbar_label) plt.show() diff --git a/pyart/correct/attenuation.py b/pyart/correct/attenuation.py index d1c9f6ab15..057c8f18ae 100644 --- a/pyart/correct/attenuation.py +++ b/pyart/correct/attenuation.py @@ -37,7 +37,7 @@ def calculate_attenuation_zphi(radar, doc=None, fzl=None, smooth_window_len=5, ---------- radar : Radar Radar object to use for attenuation calculations. Must have - phidp and refl fields. + phidp and ref fields. doc : float, optional Number of gates at the end of each ray to to remove from the calculation. @@ -243,7 +243,7 @@ def calculate_attenuation_zphi(radar, doc=None, fzl=None, smooth_window_len=5, if end_gate_arr[ray] > smooth_window_len: # extract the ray's phase shift, - # init. refl. correction and mask + # init. ref. correction and mask ray_phase_shift = corr_phidp[ray, 0:end_gate_arr[ray]] ray_mask = mask[ray, 0:end_gate_arr[ray]] ray_refl_linear = refl_linear[ray, 0:end_gate_arr[ray]] @@ -335,7 +335,7 @@ def calculate_attenuation_philinear( ---------- radar : Radar Radar object to use for attenuation calculations. Must have - phidp and refl fields. + phidp and ref fields. doc : float, optional Number of gates at the end of each ray to to remove from the calculation. @@ -922,7 +922,7 @@ def calculate_attenuation(radar, z_offset, debug=False, doc=15, fzl=4000.0, for i in range(start_ray, end_ray): # perform attenuation calculation on a single ray - # extract the ray's phase shift and init. refl. correction + # extract the ray's phase shift and init. ref. correction ray_phase_shift = proc_dp_phase_shift[i, 0:end_gate] ray_init_refl = init_refl_correct[i, 0:end_gate] diff --git a/pyart/default_config.py b/pyart/default_config.py index 5f519becd9..98f6ecb399 100644 --- a/pyart/default_config.py +++ b/pyart/default_config.py @@ -995,9 +995,9 @@ 'HCLASS': radar_echo_classification, # (55) Hydrometeor class 'HCLASS2': radar_echo_classification, # (56) Hydrometeor class 'ZDRC': corrected_differential_reflectivity, - # (57) Corrected diff. refl. + # (57) Corrected diff. ref. 'ZDRC2': corrected_differential_reflectivity, - # (58) Corrected diff. refl. + # (58) Corrected diff. ref. 'UNKNOWN_59': None, # Unknown field 'UNKNOWN_60': None, # Unknown field 'UNKNOWN_61': None, # Unknown field @@ -1290,11 +1290,11 @@ 'ZDR': corrected_differential_reflectivity, # Differential reflectivity from corrected timeseries 'UZDR': differential_reflectivity, - # Diff. refl. from uncorrected timeseries - 'AZDR': None, # Diff. refl., rainfall atten. corr., corr t.s. - 'ZDR1': None, # Diff. refl., corr. t.s., 1st LAG algo. - 'UZDR1': None, # Diff. refl., uncorr. t.s., 1st LAG algo. - 'AZDR1': None, # Diff. refl., rain. atten. corr., corr. t.s., 1st LAG + # Diff. ref. from uncorrected timeseries + 'AZDR': None, # Diff. ref., rainfall atten. corr., corr t.s. + 'ZDR1': None, # Diff. ref., corr. t.s., 1st LAG algo. + 'UZDR1': None, # Diff. ref., uncorr. t.s., 1st LAG algo. + 'AZDR1': None, # Diff. ref., rain. atten. corr., corr. t.s., 1st LAG 'PHI': corrected_differential_phase, # Differential phase from corrected timeseries diff --git a/pyart/io/tests/test_mdv_grid.py b/pyart/io/tests/test_mdv_grid.py index a290aa8791..8ca30cfea5 100644 --- a/pyart/io/tests/test_mdv_grid.py +++ b/pyart/io/tests/test_mdv_grid.py @@ -212,8 +212,8 @@ def test_mdv_degree_grid(): grid = pyart.io.read_grid_mdv( pyart.testing.MDV_GRID_FILE, file_field_names=True) - assert 'refl' in grid.fields.keys() - fdata = grid.fields['refl']['data'] + assert 'ref' in grid.fields.keys() + fdata = grid.fields['ref']['data'] assert fdata.shape == (1, 1837, 3661) assert np.ma.is_masked(fdata[0, 0, 0]) assert_almost_equal(fdata[0, 130, 2536], 20.0, 1) diff --git a/pyart/tests/custom_config.py b/pyart/tests/custom_config.py index bcb999c718..4290691fee 100644 --- a/pyart/tests/custom_config.py +++ b/pyart/tests/custom_config.py @@ -595,9 +595,9 @@ 'HCLASS': radar_echo_classification, # (55) Hydrometeor class 'HCLASS2': radar_echo_classification, # (56) Hydrometeor class 'ZDRC': corrected_differential_reflectivity, - # (57) Corrected diff. refl. + # (57) Corrected diff. ref. 'ZDRC2': corrected_differential_reflectivity, - # (58) Corrected diff. refl. + # (58) Corrected diff. ref. } diff --git a/test.py b/test.py new file mode 100644 index 0000000000..4b03c19fb8 --- /dev/null +++ b/test.py @@ -0,0 +1,181 @@ + +# test file KBGM 10:30 UTC 7 feb 2020 +import pyart +import nexradaws +import numpy as np +from scipy import ndimage +import itertools +import matplotlib.pyplot as plt +import time +import scipy + +conn = nexradaws.NexradAwsInterface() +availscans = conn.get_avail_scans('2020', '02', '07', 'KBGM') +availscans = conn.get_avail_scans('2021', '02', '07', 'KOKX') + +scan = availscans[120] +scan = availscans[122] +path = scan.create_filepath(basepath='s3://noaa-nexrad-level2/', keep_aws_structure=True)[1] +path = path.replace("\\", '/') +radar = pyart.io.read_nexrad_archive(path) + +# extract the lowest +radar = radar.extract_sweeps([0]) + +# interpolate to grid +grid = pyart.map.grid_from_radars( + (radar,), grid_shape=(1, 251, 251), + grid_limits=((0, 10000), (-200000.0, 200000.0), (-200000.0, 200000.0)), + fields=['reflectivity']) + +dx = grid.x['data'][1] - grid.x['data'][0] +dy = grid.y['data'][1] - grid.y['data'][0] + +refl_og = grid.fields['reflectivity']['data'][0,:,:] +refl_og = np.ma.masked_invalid(refl_og) +refl_og = np.ma.masked_less(refl_og, 5) + +refl_linear = 10 ** (refl_og / 10) # mm6/m3 + +snow_rate = (refl_linear/57.3)**(1/1.67) +refl = snow_rate + +refl = refl_og + + +# trying scipy filter +refl_bkg = ndimage.uniform_filter(refl, size=11) # doesn't work because of NaNs +refl_bkg = ndimage.generic_filter(refl, np.nanmean, mode='constant', cval=np.nan, size=11) +refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + +bkg_mask_bool = bkg_mask_array.astype(bool) +time1 = time.time() +refl_bkg_scipy = ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', + footprint=bkg_mask_bool, cval=np.nan) +refl_bkg_scipy = np.ma.masked_where(refl.mask, refl_bkg_scipy) +time2 = time.time() +print(time2-time1) + +# Create idealized arrays +refl_test = np.empty_like(refl) +refl_test[:] = 30 +refl_test[115:125,115:125] = 50 + +# create idealized array (gradient grid with gradient background) +# create background gradient +#bkg_gradient = np.linspace(0, 50, np.shape(ref)[0]) +bkg_gradient = np.linspace(0, 50, np.shape(refl)[0]) + +# block background +bkg_gradient = 5 * np.round(bkg_gradient/5) + +# constant background +bkg_gradient = np.empty_like(bkg_gradient) +bkg_gradient[:] = 25 +# repeat array over other dimension +refl_test = np.array(np.shape(refl)[1]*[bkg_gradient]) +# given a 240 x 240 array, let's set the cores (5x5) +core_gradient = np.linspace(25, 50, 5) +dn = int(np.floor(np.shape(refl_test)[0]/5)) +indices = np.arange(int(dn/2), int(dn/2+5*dn), dn) +for ind in itertools.product(indices, indices): + core_ind = np.where(indices==ind[0])[0][0] + refl_test[ind[0]-3:ind[0]+3, ind[1]-3:ind[1]+3] = core_gradient[core_ind] + #ref_test[ind[0], ind[1]] = core_gradient[core_ind] + #print(ind[0]) + +refl = refl_test.T +refl = np.ma.masked_invalid(refl) + +# plot +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(refl, vmin=0, vmax=50) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(ze_bkg, vmin=0, vmax=50) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(conv_core_array, vmin=0, vmax=3) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(conv_strat_array, vmin=0, vmax=2) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(convRadiuskm, vmin=1, vmax=5) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(zeDiff, vmin=0, vmax=50) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(refl, vmin=0, vmax=12) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(ze_bkg_test, vmin=0, vmax=10)#, cmap='magma_r') +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +fig.savefig("Q:\\My Drive\\phd\\winter_storms\\nexrad_stitching\\convsf_imgs\\testing\\ze_bkg_10km_kbgm.png", dpi=600, bbox_inches='tight') +#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(conv_core_array, vmin=0, vmax=3) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +# plot +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(snow_rate, vmin=0, vmax=10) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(refl_bkg_scipy, vmin=0, vmax=10) +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + +fig = plt.figure() +ax = plt.axes() +cs = ax.pcolormesh(bkg_diff, vmin=-5e-7, vmax=5e-7, cmap='RdBu_r') +cbar = plt.colorbar(cs) +ax.set_aspect('equal') +plt.show() + + From 6fff794b27741604efbfcea17f89db486a45b771 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 14 Sep 2022 15:07:02 -0400 Subject: [PATCH 04/66] Create yuter_convsf.py --- pyart/retrieve/yuter_convsf.py | 460 +++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 pyart/retrieve/yuter_convsf.py diff --git a/pyart/retrieve/yuter_convsf.py b/pyart/retrieve/yuter_convsf.py new file mode 100644 index 0000000000..c13dabf705 --- /dev/null +++ b/pyart/retrieve/yuter_convsf.py @@ -0,0 +1,460 @@ +import numpy as np +import scipy.ndimage + +def radialDistanceMask_array(mask_array, minradiuskm, maxradiuskm, x_pixsize, + y_pixsize, centerx, centery, circular=True): + """ + Computes a radial distance mask, everything with distance between minradiuskm and maxradiuskm is assigned 1, everything else is assigned 0. This version can handle rectangular arrays and pixels as well as square ones. + + + Parameters + ---------- + mask_array : array + Array to mask + minradiuskm, maxradiuskm : float + The minimum and maximum radius of the non-maked region in kilometers. + x_pixsize, y_pixsize : float + The pixel size in the x- and y-dimension in kilometers, respectively + centerx, centery : int + The center pixel in the x- and y-dimension, respectively + + Returns + ------- + mask_array : array + Rectangular array masked by a radial distance. + """ + + xsize, ysize = mask_array.shape + + for j in np.arange(0, ysize, 1): + for i in np.arange(0, xsize, 1): + # compute range to pixel + if circular: + x_range_sq = ((centerx - i) * x_pixsize) ** 2 + y_range_sq = ((centery - j) * y_pixsize) ** 2 + range = np.sqrt(x_range_sq + y_range_sq) + # if circular is False, use square mask + else: + x_range = abs(int(np.floor(centerx - i) * x_pixsize)) + y_range = abs(int(np.floor(centery - j) * y_pixsize)) + + if x_range > y_range: + range = x_range + else: + range = y_range + # if range is within min and max, set to True + if (range <= maxradiuskm) and (range >= minradiuskm): + mask_array[j, i] = 1 + else: + mask_array[j, i] = 0 + + return mask_array + + +def backgroundIntensity(refl, index_x, index_y, bkgDiameter, mask_array, dBaveraging): + """ + For a pixel in the array determine the average intensity of the surrounding pixels in the window. The window is passed in as mask_array + + Parameters + ---------- + refl : array + Reflectivity array to compute average + index_x, index_y : int + x- and y-dimension index, respectively + bkgDiameter : int + diameter to compute average in pixels + mask_array : array + Array of radial points to use for average + dBaveraging : bool + If True, converts dBZ to linear Z before averaging + + Returns + ------- + mean : float + The average value for the given index + """ + + running_total = 0 + mean = np.nan + pixelradius = 0 + numpixels = 0 + minptsAver = 1 + aval = 0 + centerval = 0 + maski = 0 + maskj = 0 + + xsize, ysize = refl.shape + # only compute background value if there is data at the point + centerval = refl[index_y, index_x] + if np.ma.is_masked(centerval): + return mean + + pixelradius = int(np.floor(bkgDiameter / 2)) + + # note the window will only be centered on point if windim is odd + if pixelradius == (bkgDiameter / 2.0): + print('Warning: windim = {0} is EVEN, background window will not be centered on point\n'.format(bkgDiameter)) + + if (index_x >= 0) and (index_x < xsize) and (index_y >= 0) and (index_y < ysize): + + # loop through point in background window + for j in np.arange(index_y - pixelradius, index_y + pixelradius + 1, 1): + maski = 0 + for i in np.arange(index_x - pixelradius, index_x + pixelradius + 1, 1): + # check that point is within bounds and activated in mask + if (i >= 0) and (i < xsize) and (j >= 0) and (j < ysize) and mask_array[maskj, maski] == 1: + aval = refl[j, i] + + if not np.ma.is_masked(aval): + numpixels += 1 + # converting to linear Z + if dBaveraging: + running_total += 10 ** (aval / 10) + else: + running_total += aval + maski += 1 + maskj += 1 + + if numpixels >= minptsAver: + mean = running_total / numpixels + # converting to dBZ + if dBaveraging: + mean = 10 * np.log10(mean) + + return mean + +def backgroundIntensity_array(refl, mask_array, dBaveraging): + """ + For a pixel in the array determine the average intensity of the surrounding pixels in the window. + The window is passed in as mask_array + + Parameters + ---------- + refl : array + Reflectivity array to compute average + mask_array : array + Array of radial points to use for average + dBaveraging : bool + If True, converts dBZ to linear Z before averaging + + Returns + ------- + refl_bkg : array + Array of average values + """ + + # if dBaverage is true, convert reflectivity to linear Z + if dBaveraging: + refl = 10 ** (refl / 10) + + # calculate background reflectivity with circular footprint + refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', + footprint=mask_array.astype(bool), cval=np.nan) + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + # if dBaveraging is true, convert background reflectivity to dBZ + if dBaveraging: + refl_bkg = 10 * np.log10(refl_bkg) + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + return refl_bkg + +def convcore_cos_scheme(zeVal, ze_bkg, minZeDiff, convThresB, alwaysConvThres, CS_CORE): + # otherconvthres = absconvthres + # intense = truncZconvthres + """ + Cosine scheme for determining is convective core + + Parameters + ---------- + zeVal : float + Reflectivity value of point + ze_bkg : float + Reflectivity value of background value + minZediff : float + Minimum difference between zeVal and ze_bkg needed for convective classification + convThresB : float + Convective threshold used in the cosine function + alwaysConvThres : float + All values above this threshold considered to be convective + + Returns + ------- + is_core : bool + Boolean if point is convective (1) or not (0) + """ + + # initialize to not a convective core + is_core = 0 + + # if zval is greater than or equal to intense, set to core + if zeVal >= alwaysConvThres: + is_core = CS_CORE + elif (not np.ma.is_masked(zeVal)) and (not np.ma.is_masked(ze_bkg)): + # if background is less than zero, set difference to min difference + if ze_bkg < 0: + zeDiff = minZeDiff + # else, use function from Yuter et al. (1997) + else: + zeDiff = minZeDiff * np.cos((np.pi * ze_bkg) / (2 * convThresB)) + # if difference is less than zero, set to zero + if zeDiff < 0: + zeDiff = 0 + # if value minus background is greater than or equal to difference, set to core + if (zeVal - ze_bkg) >= zeDiff: + is_core = CS_CORE + + return is_core + +def convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThres, CS_CORE): + # otherconvthres = absconvthres + # intense = truncZconvthres + """ + Cosine scheme for determining is convective core + + Parameters + ---------- + zeVal : float + Reflectivity value of point + ze_bkg : float + Reflectivity value of background value + minZediff : float + Minimum difference between zeVal and ze_bkg needed for convective classification + convThresB : float + Convective threshold used in the cosine function + alwaysConvThres : float + All values above this threshold considered to be convective + + Returns + ------- + is_core : bool + Boolean if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zeDiff for entire array + zeDiff = minZeDiff * np.cos((np.pi * ze_bkg) / (2 * convThresB)) + zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero + zeDiff[ze_bkg < 0] = minZeDiff # where background less than zero, set to min diff + + # set values + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[(refl - ze_bkg) >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + +def convcore_scaled_array(refl, ze_bkg, minZeFactor, alwaysConvThres, CS_CORE): + # otherconvthres = absconvthres + # intense = truncZconvthres + """ + Cosine scheme for determining is convective core + + Parameters + ---------- + zeVal : float + Reflectivity value of point + ze_bkg : float + Reflectivity value of background value + minZediff : float + Minimum difference between zeVal and ze_bkg needed for convective classification + convThresB : float + Convective threshold used in the cosine function + alwaysConvThres : float + All values above this threshold considered to be convective + + Returns + ------- + is_core : bool + Boolean if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zeDiff for entire array + zeDiff = minZeFactor * ze_bkg + zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero + zeDiff[ze_bkg < 0] = 0 # where background less than zero, set to zero + + # set values + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + +def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + refl : array + Array of reflectivity values + conv_strat_array : array + Array with convective stratiform classifications + conv_core_array : array + Array with convective cores + NOSFCECHO : int + Value to assign points classified as no surface echo + CONV : int + Value to assign points classified as convective + SF : int + Value to assign points classified as stratiform + WEAKECHO : int + Value to assign points classfied as weak echo + CS_CORE : int + Value assigned to convective cores in conv_core_array + MINDBZUSE : float + Minimum dBZ value to consider in classification, all values below this will be set to NOSFCECHO + WEAKECHOTHRES : float + dBZ threshold for weak echo classification, all values below this will be set to WEAKECHO + + Returns + ------- + mean : conv_strat_array + conv_strat_array with initial classifications + """ + + # assuming order so that each point is only assigned one time, no overlapping assignment + # initially, assign every point to stratiform + conv_strat_array[:] = SF + # where reflectivity is masked, set to no surface echo + conv_strat_array[refl.mask] = NOSFCECHO + # assign convective cores to CONV + conv_strat_array[conv_core_array==CS_CORE] = CONV + # assign reflectivity less than weakechothres to weak echo + conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO + # assign reflectivity less than minimum to no surface echo + conv_strat_array[refl < MINDBZUSE] = NOSFCECHO + + return conv_strat_array + +def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + maxConvDiameter : int + maximum convective diameter in kilometers + radius_km : int + convective radius in kilometers + xpacing, yspacing : float + x- and y-dimension pixel size in meters, respectively + centerConvMask_x : int + index of center point + + Returns + ------- + mean : conv_mask_array + array masked based on distance of convective diameter + """ + + conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) + conv_mask_array = radialDistanceMask_array(conv_mask_array, 0, radius_km, xspacing, yspacing, + centerConvMask_x, centerConvMask_x, True) + + return conv_mask_array + +def assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius, maxConvRadius=5): + # alternative version for assigning convective radii + # returns array the same size as ze_bkg with values for convective radii + """ + Assigns the convective radius in kilometers based on the background reflectivity + + Parameters + ---------- + ze_bkg : array + array of background reflectivity values + dBZformaxconvradius : float + reflectivity value for maximum convective radius (5 km) + maxConvRadius : float, optional + maximum convective radius in kilometers + + Returns + ------- + convRadiuskm : array + array of convective radii based on background values and dBZ for max. conv radius + """ + + convRadiuskm = np.ones_like(ze_bkg) + + convRadiuskm[ze_bkg >= (dBZformaxconvradius - 15)] = maxConvRadius - 3 + convRadiuskm[ze_bkg >= (dBZformaxconvradius - 10)] = maxConvRadius - 2 + convRadiuskm[ze_bkg >= (dBZformaxconvradius - 5)] = maxConvRadius - 1 + convRadiuskm[ze_bkg >= dBZformaxconvradius] = maxConvRadius + + return convRadiuskm + + +def incorporateConvRadius(conv_strat_array, index_x, index_y, maxConvDiameter, conv_mask_array, NOSFCECHO, CONV): + """ + Assigns the area within the convective radius around the convective core as convective + function similar to backgroundIntensity except assigns value to mask array points in conv_strat_array + only passing in legit convcore points to less error checking + should only be called if convradius > 1 + + Parameters + ---------- + conv_strat_array : array + Array with convective stratiform classifications + index_x, index_y : int + x- and y-dimension index, respectfully + maxConvDiameter : int + diameter to assign convective + conv_mask_array : array + Masked array of conv_strat_array + + Returns + ------- + mean : conv_strat_array + conv_strat_array with convective radii applied + """ + + xsize, ysize = conv_strat_array.shape + + maski = 0 + maskj = 0 + + # note the convradius is always odd as it includes the conv core pixel at the center + pixelradius = int(np.floor(maxConvDiameter / 2)) + + # check limits of requested point are within array bounds + if (index_x >= 0) and (index_x < xsize) and (index_y >= 0) and (index_y < ysize): + + # loop through the points in the square window in full size map + for j in np.arange(index_y - pixelradius, index_y + pixelradius + 1, 1): + maski = 0 + for i in np.arange(index_x - pixelradius, index_x + pixelradius + 1, 1): + # check that point is within bounds, activated in mask, and has echo + if (i >= 0) and (i < xsize) and (j >= 0) and (j < ysize) and \ + conv_mask_array[maskj, maski] == 1 and conv_strat_array[j, i] != NOSFCECHO: + # assign pixel as convective + conv_strat_array[j,i] = CONV + maski += 1 + maskj += 1 + + return conv_strat_array + + +# def assignConvRadiuskm(ze_bkg, dBZformaxconvradius, maxconvradius=5): +# # version for single background points +# # returns single convective radii for given background value +# if ze_bkg >= dBZformaxconvradius: +# convRadiuskm = maxconvradius +# elif ze_bkg >= (dBZformaxconvradius - 5): +# convRadiuskm = maxconvradius - 1 +# elif ze_bkg >= (dBZformaxconvradius - 10): +# convRadiuskm = maxconvradius - 2 +# elif ze_bkg >= (dBZformaxconvradius - 15): +# convRadiuskm = maxconvradius - 3 +# else: +# convRadiuskm = maxconvradius - 4 +# +# return convRadiuskm From c5731292c5a31374a658e1e90aec0f63b1115242 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 14 Sep 2022 15:07:06 -0400 Subject: [PATCH 05/66] Update _echo_class_updated.py --- pyart/retrieve/_echo_class_updated.py | 407 +++++++++++++------------- 1 file changed, 201 insertions(+), 206 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 826d340e89..3b855f1e53 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -1,216 +1,211 @@ import numpy as np - - -def _steiner_conv_strat(refl, x, y, dx, dy, intense=42, peak_relation=0, - area_relation=1, bkg_rad=11000, use_intense=True): +import sys +sys.path.append('C:\\Users\\lmtomkin\\Documents\\GitHub\\pyart_convsf\\pyart\\retrieve\\') + +import yuter_convsf +import time +import scipy.ndimage + +def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, + useCosine=True, minZeDiff=8, convThresB=55, scalarDiff=1.5, + weakEchoThres=5.0, minDBZused=5.0, applyLargeRadialMask=False, + largeRadialMask_minRadkm=0, largeRadialMask_maxRadkm=170, + dBZforMaxConvRadius = 30, maxConvRad_km = 5.0, dBaveraging=False, incorp_rad=False): """ We perform the Steiner et al. (1995) algorithm for echo classification using only the reflectivity field in order to classify each grid point as either convective, stratiform or undefined. Grid points are classified as follows, - 0 = Undefined + 0 = No Surface Echo/ Undefined 1 = Stratiform 2 = Convective + 3 = Weak Echo + + ref : array + array of reflectivity values + x, y : array + x and y coordinates of reflectivity array, respectively + dx, dy : float + The x- and y-dimension resolutions in meters, respectively. + alwaysConvThres : float, optional + Threshold for points that are always convective. All values above the threshold are classifed as convective + minZeDiff : float, optional + Minimum difference between background average and reflectivity in order to be classified as convective. + a value in Yuter et al. (2005) + convThresB : float, optional + Convective threshold used in cosine function for classifying convective vs. stratiform + b value in Yuter et al. (2005) + bkg_rad : float, optional + Radius to compute background reflectivity in kilometers. Default is 11 km + maxConvRad_km : float, optional + Maximum radius around convective cores to classify as convective. Default is 5 km + weakEchoThres : float, optional + Threshold for determining weak echo. All values below this threshold will be considered weak echo + minDBZused : float, optional + Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo + applyRadialMask : bool, optional + Flag to set a radial mask for algorithm + dBZforMaxConvRadius : float, optional + dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius + dBaveraging : bool, optional + True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + """ - def convective_radius(ze_bkg, area_relation): - """ - Given a mean background reflectivity value, we determine via a step - function what the corresponding convective radius would be. - - Higher background reflectivitives are expected to have larger - convective influence on surrounding areas, so a larger convective - radius would be prescribed. - """ - if area_relation == 0: - if ze_bkg < 30: - conv_rad = 1000. - elif (ze_bkg >= 30) & (ze_bkg < 35.): - conv_rad = 2000. - elif (ze_bkg >= 35.) & (ze_bkg < 40.): - conv_rad = 3000. - elif (ze_bkg >= 40.) & (ze_bkg < 45.): - conv_rad = 4000. - else: - conv_rad = 5000. - - if area_relation == 1: - if ze_bkg < 25: - conv_rad = 1000. - elif (ze_bkg >= 25) & (ze_bkg < 30.): - conv_rad = 2000. - elif (ze_bkg >= 30.) & (ze_bkg < 35.): - conv_rad = 3000. - elif (ze_bkg >= 35.) & (ze_bkg < 40.): - conv_rad = 4000. - else: - conv_rad = 5000. - - if area_relation == 2: - if ze_bkg < 20: - conv_rad = 1000. - elif (ze_bkg >= 20) & (ze_bkg < 25.): - conv_rad = 2000. - elif (ze_bkg >= 25.) & (ze_bkg < 30.): - conv_rad = 3000. - elif (ze_bkg >= 30.) & (ze_bkg < 35.): - conv_rad = 4000. - else: - conv_rad = 5000. - - if area_relation == 3: - if ze_bkg < 40: - conv_rad = 0. - elif (ze_bkg >= 40) & (ze_bkg < 45.): - conv_rad = 1000. - elif (ze_bkg >= 45.) & (ze_bkg < 50.): - conv_rad = 2000. - elif (ze_bkg >= 50.) & (ze_bkg < 55.): - conv_rad = 6000. - else: - conv_rad = 8000. - - return conv_rad - - def peakedness(ze_bkg, peak_relation): - """ - Given a background reflectivity value, we determine what the necessary - peakedness (or difference) has to be between a grid point's - reflectivity and the background reflectivity in order for that grid - point to be labeled convective. - """ - if peak_relation == 0: - if ze_bkg < 0.: - peak = 10. - elif (ze_bkg >= 0.) and (ze_bkg < 42.43): - peak = 10. - ze_bkg ** 2 / 180. - else: - peak = 0. - - elif peak_relation == 1: - if ze_bkg < 0.: - peak = 14. - elif (ze_bkg >= 0.) and (ze_bkg < 42.43): - peak = 14. - ze_bkg ** 2 / 180. - else: - peak = 4. - - return peak - - sclass = np.zeros(refl.shape, dtype=int) - ny, nx = refl.shape - - for i in range(0, nx): - # Get stencil of x grid points within the background radius - imin = np.max(np.array([1, (i - bkg_rad / dx)], dtype=int)) - imax = np.min(np.array([nx, (i + bkg_rad / dx)], dtype=int)) - - for j in range(0, ny): - # First make sure that the current grid point has not already been - # classified. This can happen when grid points within the - # convective radius of a previous grid point have also been - # classified. - if ~np.isnan(refl[j, i]) & (sclass[j, i] == 0): - # Get stencil of y grid points within the background radius - jmin = np.max(np.array([1, (j - bkg_rad / dy)], dtype=int)) - jmax = np.min(np.array([ny, (j + bkg_rad / dy)], dtype=int)) - - n = 0 - sum_ze = 0 - - # Calculate the mean background reflectivity for the current - # grid point, which will be used to determine the convective - # radius and the required peakedness. - - for l in range(imin, imax): - for m in range(jmin, jmax): - if not np.isnan(refl[m, l]): - rad = np.sqrt( - (x[l] - x[i]) ** 2 + (y[m] - y[j]) ** 2) - - # The mean background reflectivity will first be - # computed in linear units, i.e. mm^6/m^3, then - # converted to decibel units. - if rad <= bkg_rad: - n += 1 - sum_ze += 10. ** (refl[m, l] / 10.) - - if n == 0: - ze_bkg = np.inf - else: - ze_bkg = 10.0 * np.log10(sum_ze / n) - - # Now get the corresponding convective radius knowing the mean - # background reflectivity. - conv_rad = convective_radius(ze_bkg, area_relation) - - # Now we want to investigate the points surrounding the current - # grid point that are within the convective radius, and whether - # they too are convective, stratiform or undefined. - - # Get stencil of x and y grid points within the convective - # radius. - lmin = np.max( - np.array([1, int(i - conv_rad / dx)], dtype=int)) - lmax = np.min( - np.array([nx, int(i + conv_rad / dx)], dtype=int)) - mmin = np.max( - np.array([1, int(j - conv_rad / dy)], dtype=int)) - mmax = np.min( - np.array([ny, int(j + conv_rad / dy)], dtype=int)) - - if use_intense and (refl[j, i] >= intense): - sclass[j, i] = 2 - - for l in range(lmin, lmax): - for m in range(mmin, mmax): - if not np.isnan(refl[m, l]): - rad = np.sqrt( - (x[l] - x[i]) ** 2 - + (y[m] - y[j]) ** 2) - - if rad <= conv_rad: - sclass[m, l] = 2 - - else: - peak = peakedness(ze_bkg, peak_relation) - - if refl[j, i] - ze_bkg >= peak: - sclass[j, i] = 2 - - for l in range(imin, imax): - for m in range(jmin, jmax): - if not np.isnan(refl[m, l]): - rad = np.sqrt( - (x[l] - x[i]) ** 2 - + (y[m] - y[j]) ** 2) - - if rad <= conv_rad: - sclass[m, l] = 2 - - else: - # If by now the current grid point has not been - # classified as convective by either the intensity - # criteria or the peakedness criteria, then it must be - # stratiform. - sclass[j, i] = 1 - - return sclass - - -def steiner_class_buff(ze, x, y, z, dx, dy, bkg_rad, - work_level, intense, peak_relation, - area_relation, use_intense): - - zslice = np.argmin(np.abs(z - work_level)) - refl = ze[zslice, :, :] - - area_rel = {"small": 0, "medium": 1, "large": 2, "sgp": 3} - peak_rel = {"default": 0, "sgp": 1} - - sclass = _steiner_conv_strat(refl, x, y, dx, dy, intense=intense, - peak_relation=peak_rel[peak_relation], - area_relation=area_rel[area_relation], - bkg_rad=11000, use_intense=True) - - return sclass + + if maxConvRad_km > 5: + print("Max conv radius must be less than 5 km, exiting") + # quit + + #%% Set up arrays and values for convective stratiform algorithm + # create Ze arrays for under and overestimate + # need to run through 3 times - need to figure out best way to do this + # refl = grid.fields['reflectivity']['data'][0,:,:] + # refl = np.ma.masked_invalid(refl) + # refl_under = refl.copy() - 5 + # refl_over = refl.copy() + 5 + # loop through 3 times + t1 = time.time() + # create empty arrays + ze_bkg = np.zeros(refl.shape, dtype=float) + conv_core_array = np.zeros(refl.shape, dtype=float) + conv_strat_array = np.zeros(refl.shape, dtype=float) + mask_array = np.zeros(refl.shape, dtype=float) + + # fill with missing (nan) + ze_bkg[:] = np.nan + conv_core_array[:] = np.nan + conv_strat_array[:] = np.nan + mask_array[:] = np.nan + + # Constants to fill arrays with + CS_CORE = 3 + NOSFCECHO = 0 + WEAKECHO = 3 + SF = 1 + CONV = 2 + t2 = time.time() + print("Time to set up arrays: {0} seconds".format(t2-t1)) + #%% Set up mask arrays + t1 = time.time() + # prepare for convective mask arrays + # calculate maximum convective diameter from max. convective radius (input) + maxConvDiameter = int(np.floor((maxConvRad_km / (dx / 1000)) * 2)) + # if diameter is even, make odd + if maxConvDiameter % 2 == 0: maxConvDiameter = maxConvDiameter + 1 + # find center point + centerConvMask_x = int(np.floor(maxConvDiameter / 2)) + + # prepare background mask array for computing background average + # calculate number of pixels for background array given requested background radius and dx + bkgDiameter_pix = int(np.floor((bkgRad_km / (dx / 1000)) * 2)) + # set diameter to odd if even + if bkgDiameter_pix % 2 == 0: bkgDiameter_pix = bkgDiameter_pix + 1 + # find center point + bkg_center = int(np.floor(bkgDiameter_pix / 2)) + # create background array + bkg_mask_array = np.ones((bkgDiameter_pix, bkgDiameter_pix), dtype=float) + # mask outside circular region + bkg_mask_array = yuter_convsf.radialDistanceMask_array(bkg_mask_array, minradiuskm=0, maxradiuskm=bkgRad_km, + x_pixsize=dx / 1000, y_pixsize=dy / 1000, + centerx=bkg_center, centery=bkg_center, circular=True) + + # Create large mask array for determining where to calculate convective stratiform + # initialize array with 1 (calculate convective stratiform over entire array) + mask_array[:] = 1 + # if True, create radial mask + if applyLargeRadialMask: + mask_array = yuter_convsf.radialDistanceMask_array(mask_array, largeRadialMask_minRadkm, largeRadialMask_maxRadkm, + x_pixsize=dx/1000, y_pixsize=dy/1000, centerx=int(np.floor(refl.shape[0] / 2)), + centery=int(np.floor(refl.shape[1] / 2)), circular=True) + + t2 = time.time() + print("Time to set up mask arrays: {0} seconds".format(t2 - t1)) + #%% Convective stratiform detection + t1 = time.time() + # Compute background radius + ze_bkg = yuter_convsf.backgroundIntensity_array(refl, bkg_mask_array, dBaveraging) + # mask background average + ze_bkg = np.ma.masked_where(refl.mask, ze_bkg) + + t2 = time.time() + print("Time to calculate background average: {0} seconds".format(t2 - t1)) + + t1 = time.time() + # Get convective core array from cosine scheme, or scalar scheme + if useCosine: + conv_core_array = yuter_convsf.convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThres, + CS_CORE) + else: + conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE) + + t2 = time.time() + print("Time to get convective cores: {0} seconds".format(t2 - t1)) + # count convective cores + corecount = np.count_nonzero(conv_core_array) + + t1 = time.time() + # Do an initial assignment of convsf array + conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + minDBZused, weakEchoThres) + t2 = time.time() + print("Time to do initial convective stratiform assignment: {0} seconds".format(t2 - t1)) + + # Assign convective radii based on background reflectivity + convRadiuskm = yuter_convsf.assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius=dBZforMaxConvRadius, maxConvRadius=maxConvRad_km) + + if incorp_rad: + t1 = time.time() + # Loop through array for a final time to incorporate the convective radii + for j in np.arange(0, convRadiuskm.shape[1], 1): + + for i in np.arange(0, convRadiuskm.shape[0], 1): + + # if point is a convective core, find radius, get convective mask radius and incorporate radius + if conv_core_array[j, i] == CS_CORE: + convRadius = np.floor(convRadiuskm[j,i]) + + if convRadius == 1: + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, centerConvMask_x) + elif convRadius > 1: + if convRadius == 2: + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, centerConvMask_x) + elif convRadius == 3: + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, centerConvMask_x) + elif convRadius == 4: + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, centerConvMask_x) + elif convRadius == 5: + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, centerConvMask_x) + + conv_strat_array = yuter_convsf.incorporateConvRadius(conv_strat_array, i, j, maxConvDiameter, + conv_mask_array, NOSFCECHO, CONV) + + t2 = time.time() + print("Time to apply convective radius: {0} seconds".format(t2 - t1)) + + return ze_bkg, conv_core_array, conv_strat_array + + # go through map array once to compute the background intensity and find convective cores + # pixel by pixel method + # for j in np.arange(0, ysize_refl, 1): + # for i in np.arange(0, xsize_refl, 1): + # + # # compute background value + # ze_bkg[j, i] = yuter_convsf.backgroundIntensity(refl, i, j, bkgDiameter_pix, bkg_mask_array, dBaveraging) + # + # # identify convective cores + # if use_cosine: + # conv_core_array[j, i] = yuter_convsf.convcore_cos_scheme(refl[j, i], ze_bkg[j, i], minZeDiff, convThresB, alwaysConvThres, CS_CORE) + + # else: + # mask_array = yuter_convsf.radialDistanceMask_array(mask_array, minRad_km, maxRad_km, + # x_pixsize=dx/1000, y_pixsize=dy/1000, centerx=int(np.floor(xsize_refl / 2)), + # centery=int(np.floor(ysize_refl / 2)), circular=False) + + # initialize mask arrays (1-5 km) + # convmaskarray1 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, centerConvMask_x) + # convmaskarray2 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, centerConvMask_x) + # convmaskarray3 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, centerConvMask_x) + # convmaskarray4 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, centerConvMask_x) + # convmaskarray5 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, centerConvMask_x) From be7f5863f140aa902de9576adf6063c9f1fe2a0a Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 14 Sep 2022 16:21:49 -0400 Subject: [PATCH 06/66] Implement dilation technique for core radius assignment --- pyart/retrieve/_echo_class_updated.py | 97 ++++++++++++++++++--------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 3b855f1e53..681dd9d3be 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -144,45 +144,45 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # count convective cores corecount = np.count_nonzero(conv_core_array) - t1 = time.time() - # Do an initial assignment of convsf array - conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - minDBZused, weakEchoThres) - t2 = time.time() - print("Time to do initial convective stratiform assignment: {0} seconds".format(t2 - t1)) + # t1 = time.time() + # # Do an initial assignment of convsf array + # conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + # NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + # minDBZused, weakEchoThres) + # t2 = time.time() + # print("Time to do initial convective stratiform assignment: {0} seconds".format(t2 - t1)) # Assign convective radii based on background reflectivity + t1 = time.time() convRadiuskm = yuter_convsf.assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius=dBZforMaxConvRadius, maxConvRadius=maxConvRad_km) - if incorp_rad: - t1 = time.time() - # Loop through array for a final time to incorporate the convective radii - for j in np.arange(0, convRadiuskm.shape[1], 1): - - for i in np.arange(0, convRadiuskm.shape[0], 1): + # Incorporate convective radius using binary dilation + # Create empty array for assignment + temp_assignment = np.zeros_like(conv_core_array) + + # Loop through radii + for radius in np.arange(1, maxConvRad_km+1): + # create mask array for radius incorporation + conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) + # find location of radius + temp = convRadiuskm == radius + # get cores for given radius + temp_core = np.ma.masked_where(~temp, conv_core_array) + # dilate cores + temp_dilated = scipy.ndimage.binary_dilation(temp_core.filled(0), conv_mask_array) + # add to assignment array + temp_assignment = temp_assignment + temp_dilated + + # add dilated cores to original array + conv_core_array[temp_assignment>=1] = CS_CORE - # if point is a convective core, find radius, get convective mask radius and incorporate radius - if conv_core_array[j, i] == CS_CORE: - convRadius = np.floor(convRadiuskm[j,i]) - - if convRadius == 1: - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, centerConvMask_x) - elif convRadius > 1: - if convRadius == 2: - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, centerConvMask_x) - elif convRadius == 3: - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, centerConvMask_x) - elif convRadius == 4: - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, centerConvMask_x) - elif convRadius == 5: - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, centerConvMask_x) - - conv_strat_array = yuter_convsf.incorporateConvRadius(conv_strat_array, i, j, maxConvDiameter, - conv_mask_array, NOSFCECHO, CONV) + t2 = time.time() + print("Time to apply convective radius: {0} seconds".format(t2 - t1)) - t2 = time.time() - print("Time to apply convective radius: {0} seconds".format(t2 - t1)) + # Now do convective stratiform classification + conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + minDBZused, weakEchoThres) return ze_bkg, conv_core_array, conv_strat_array @@ -209,3 +209,34 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # convmaskarray3 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, centerConvMask_x) # convmaskarray4 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, centerConvMask_x) # convmaskarray5 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, centerConvMask_x) + +# if incorp_rad: +# t1 = time.time() +# # Loop through array for a final time to incorporate the convective radii +# for j in np.arange(0, convRadiuskm.shape[1], 1): +# +# for i in np.arange(0, convRadiuskm.shape[0], 1): +# +# # if point is a convective core, find radius, get convective mask radius and incorporate radius +# if conv_core_array[j, i] == CS_CORE: +# convRadius = np.floor(convRadiuskm[j, i]) +# +# if convRadius == 1: +# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, +# centerConvMask_x) +# elif convRadius > 1: +# if convRadius == 2: +# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, +# centerConvMask_x) +# elif convRadius == 3: +# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, +# centerConvMask_x) +# elif convRadius == 4: +# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, +# centerConvMask_x) +# elif convRadius == 5: +# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, +# centerConvMask_x) +# +# conv_strat_array = yuter_convsf.incorporateConvRadius(conv_strat_array, i, j, maxConvDiameter, +# conv_mask_array, NOSFCECHO, CONV) \ No newline at end of file From 899aa5658bc962dadceb364c685c391cccd0a6dd Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 14 Sep 2022 16:34:43 -0400 Subject: [PATCH 07/66] Add functionality for scalar addition difference --- pyart/retrieve/_echo_class_updated.py | 4 ++-- pyart/retrieve/yuter_convsf.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 681dd9d3be..47f3d81a38 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -7,7 +7,7 @@ import scipy.ndimage def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, - useCosine=True, minZeDiff=8, convThresB=55, scalarDiff=1.5, + useCosine=True, minZeDiff=8, convThresB=55, scalarDiff=1.5, addFlag=False, weakEchoThres=5.0, minDBZused=5.0, applyLargeRadialMask=False, largeRadialMask_minRadkm=0, largeRadialMask_maxRadkm=170, dBZforMaxConvRadius = 30, maxConvRad_km = 5.0, dBaveraging=False, incorp_rad=False): @@ -137,7 +137,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, conv_core_array = yuter_convsf.convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThres, CS_CORE) else: - conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE) + conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addFlag) t2 = time.time() print("Time to get convective cores: {0} seconds".format(t2 - t1)) diff --git a/pyart/retrieve/yuter_convsf.py b/pyart/retrieve/yuter_convsf.py index c13dabf705..634bd0042b 100644 --- a/pyart/retrieve/yuter_convsf.py +++ b/pyart/retrieve/yuter_convsf.py @@ -248,7 +248,7 @@ def convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThr return conv_core_array -def convcore_scaled_array(refl, ze_bkg, minZeFactor, alwaysConvThres, CS_CORE): +def convcore_scaled_array(refl, ze_bkg, minZeFactor, alwaysConvThres, CS_CORE, addition=False): # otherconvthres = absconvthres # intense = truncZconvthres """ @@ -277,7 +277,10 @@ def convcore_scaled_array(refl, ze_bkg, minZeFactor, alwaysConvThres, CS_CORE): conv_core_array = np.zeros_like(refl) # calculate zeDiff for entire array - zeDiff = minZeFactor * ze_bkg + if addition: + zeDiff = minZeFactor + ze_bkg + else: + zeDiff = minZeFactor * ze_bkg zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero zeDiff[ze_bkg < 0] = 0 # where background less than zero, set to zero From 73ef2d3f6d2054f11e0fadc844b0e6b48aaf25df Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 14:41:01 -0400 Subject: [PATCH 08/66] Rename variables --- pyart/retrieve/_echo_class_updated.py | 64 +++++++++------------------ 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 47f3d81a38..75588d71a0 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -7,10 +7,14 @@ import scipy.ndimage def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, - useCosine=True, minZeDiff=8, convThresB=55, scalarDiff=1.5, addFlag=False, - weakEchoThres=5.0, minDBZused=5.0, applyLargeRadialMask=False, - largeRadialMask_minRadkm=0, largeRadialMask_maxRadkm=170, - dBZforMaxConvRadius = 30, maxConvRad_km = 5.0, dBaveraging=False, incorp_rad=False): + useCosine=True, maxDiff=8, zeroDiffCosValue=55, + weakEchoThres=5.0, mindBZused=5.0, + scalarDiff=1.5, addition=True, + dBaveraging=False, applyLgRadialMask=False, + lgRadialMask_minRadkm=0, lgRadialMask_maxRadkm=170, + dBZforMaxConvRad=30, maxConvRad_km=5.0, + incorpConvRad=True): + """ We perform the Steiner et al. (1995) algorithm for echo classification using only the reflectivity field in order to classify each grid point @@ -58,14 +62,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # quit #%% Set up arrays and values for convective stratiform algorithm - # create Ze arrays for under and overestimate - # need to run through 3 times - need to figure out best way to do this - # refl = grid.fields['reflectivity']['data'][0,:,:] - # refl = np.ma.masked_invalid(refl) - # refl_under = refl.copy() - 5 - # refl_over = refl.copy() + 5 - # loop through 3 times - t1 = time.time() + # create empty arrays ze_bkg = np.zeros(refl.shape, dtype=float) conv_core_array = np.zeros(refl.shape, dtype=float) @@ -84,10 +81,8 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, WEAKECHO = 3 SF = 1 CONV = 2 - t2 = time.time() - print("Time to set up arrays: {0} seconds".format(t2-t1)) + #%% Set up mask arrays - t1 = time.time() # prepare for convective mask arrays # calculate maximum convective diameter from max. convective radius (input) maxConvDiameter = int(np.floor((maxConvRad_km / (dx / 1000)) * 2)) @@ -114,47 +109,30 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # initialize array with 1 (calculate convective stratiform over entire array) mask_array[:] = 1 # if True, create radial mask - if applyLargeRadialMask: - mask_array = yuter_convsf.radialDistanceMask_array(mask_array, largeRadialMask_minRadkm, largeRadialMask_maxRadkm, + if applyLgRadialMask: + mask_array = yuter_convsf.radialDistanceMask_array(mask_array, lgRadialMask_minRadkm, lgRadialMask_maxRadkm, x_pixsize=dx/1000, y_pixsize=dy/1000, centerx=int(np.floor(refl.shape[0] / 2)), centery=int(np.floor(refl.shape[1] / 2)), circular=True) - t2 = time.time() - print("Time to set up mask arrays: {0} seconds".format(t2 - t1)) #%% Convective stratiform detection - t1 = time.time() + # Compute background radius ze_bkg = yuter_convsf.backgroundIntensity_array(refl, bkg_mask_array, dBaveraging) # mask background average ze_bkg = np.ma.masked_where(refl.mask, ze_bkg) - t2 = time.time() - print("Time to calculate background average: {0} seconds".format(t2 - t1)) - - t1 = time.time() # Get convective core array from cosine scheme, or scalar scheme if useCosine: - conv_core_array = yuter_convsf.convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThres, + conv_core_array = yuter_convsf.convcore_cos_scheme_array(refl, ze_bkg, maxDiff, zeroDiffCosValue, alwaysConvThres, CS_CORE) else: - conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addFlag) + conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addition) - t2 = time.time() - print("Time to get convective cores: {0} seconds".format(t2 - t1)) # count convective cores corecount = np.count_nonzero(conv_core_array) - # t1 = time.time() - # # Do an initial assignment of convsf array - # conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, - # NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - # minDBZused, weakEchoThres) - # t2 = time.time() - # print("Time to do initial convective stratiform assignment: {0} seconds".format(t2 - t1)) - # Assign convective radii based on background reflectivity - t1 = time.time() - convRadiuskm = yuter_convsf.assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius=dBZforMaxConvRadius, maxConvRadius=maxConvRad_km) + convRadiuskm = yuter_convsf.assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius=dBZforMaxConvRad, maxConvRadius=maxConvRad_km) # Incorporate convective radius using binary dilation # Create empty array for assignment @@ -174,15 +152,13 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, temp_assignment = temp_assignment + temp_dilated # add dilated cores to original array - conv_core_array[temp_assignment>=1] = CS_CORE - - t2 = time.time() - print("Time to apply convective radius: {0} seconds".format(t2 - t1)) + conv_core_copy = np.copy(conv_core_array) + conv_core_copy[temp_assignment >= 1] = CS_CORE # Now do convective stratiform classification - conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - minDBZused, weakEchoThres) + mindBZused, weakEchoThres) return ze_bkg, conv_core_array, conv_strat_array From 78a105f26a56cb12483e7bfbf71fb5623f66b1a6 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 14:41:15 -0400 Subject: [PATCH 09/66] Add new function --- pyart/retrieve/echo_class.py | 95 ++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index f4555687f0..e186f8575b 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -8,6 +8,7 @@ from ..config import get_fillvalue, get_field_name, get_metadata from ..exceptions import MissingOptionalDependency from ._echo_class import steiner_class_buff +from ._echo_class_updated import _revised_conv_strat from warnings import warn @@ -106,6 +107,100 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} +def conv_strat(grid, dx=None, dy=None, intense=42.0, + work_level=3000.0, peak_relation='default', + area_relation='medium', bkg_rad=11000.0, + use_intense=True, fill_value=None, + refl_field=None, estimateFlag=True, estimateOffset=5): + """ + Partition reflectivity into convective-stratiform using the Yuter + and Houze (1997) algorithm. + + Parameters + ---------- + grid : Grid + Grid containing reflectivity field to partition. + dx, dy : float, optional + The x- and y-dimension resolutions in meters, respectively. If None + the resolution is determined from the first two axes values. + intense : float, optional + The intensity value in dBZ. Grid points with a reflectivity + value greater or equal to the intensity are automatically + flagged as convective. See reference for more information. + work_level : float, optional + The working level (separation altitude) in meters. This is the height + at which the partitioning will be done, and should minimize bright band + contamination. See reference for more information. + peak_relation : 'default' or 'sgp', optional + The peakedness relation. See reference for more information. + area_relation : 'small', 'medium', 'large', or 'sgp', optional + The convective area relation. See reference for more information. + bkg_rad : float, optional + The background radius in meters. See reference for more information. + use_intense : bool, optional + True to use the intensity criteria. + fill_value : float, optional + Missing value used to signify bad data points. A value of None + will use the default fill value as defined in the Py-ART + configuration file. + refl_field : str, optional + Field in grid to use as the reflectivity during partitioning. None + will use the default reflectivity field name from the Py-ART + configuration file. + + Returns + ------- + eclass : dict + Steiner convective-stratiform classification dictionary. + + References + ---------- + Steiner, M. R., R. A. Houze Jr., and S. E. Yuter, 1995: Climatological + Characterization of Three-Dimensional Storm Structure from Operational + Radar and Rain Gauge Data. J. Appl. Meteor., 34, 1978-2007. + + """ + # Get fill value + if fill_value is None: + fill_value = get_fillvalue() + + # Parse field parameters + if refl_field is None: + refl_field = get_field_name('reflectivity') + + # parse dx and dy + if dx is None: + dx = grid.x['data'][1] - grid.x['data'][0] + if dy is None: + dy = grid.y['data'][1] - grid.y['data'][0] + + # Get coordinates + x = grid.x['data'] + y = grid.y['data'] + z = grid.z['data'] + + # Get reflectivity data + ze = np.ma.copy(grid.fields[refl_field]['data']) + ze = ze.filled(np.NaN) + + convsf_best = _revised_conv_strat(ze, x, y, z) + + if estimateFlag: + convsf_under = _revised_conv_strat(ze - estimateOffset, x, y, z) + convsf_over = _revised_conv_strat(ze + estimateOffset, x, y, z) + + return {'data': eclass.astype(np.int32), + 'standard_name': 'convsf_classification', + 'long_name': 'Convective stratiform classification', + 'valid_min': 0, + 'valid_max': 2, + 'comment_1': ('Convective-stratiform echo ' + 'classification based on ' + 'Yuter and Houze (1997)'), + 'comment_2': ('0 = Undefined, 1 = Stratiform, ' + '2 = Convective')} + + def hydroclass_semisupervised(radar, mass_centers=None, weights=np.array([1., 1., 1., 0.75, 0.5]), refl_field=None, zdr_field=None, rhv_field=None, From e39e5d90ed22e85ce3f19b50f4df8090b8a384bb Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 14:56:42 -0400 Subject: [PATCH 10/66] Add functions --- pyart/retrieve/_echo_class_updated.py | 328 ++++++++++++++++++++------ 1 file changed, 253 insertions(+), 75 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 75588d71a0..169ebb47e3 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -7,7 +7,7 @@ import scipy.ndimage def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, - useCosine=True, maxDiff=8, zeroDiffCosValue=55, + useCosine=True, maxDiff=8, zeroDiffCosVal=55, weakEchoThres=5.0, mindBZused=5.0, scalarDiff=1.5, addition=True, dBaveraging=False, applyLgRadialMask=False, @@ -64,13 +64,13 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, #%% Set up arrays and values for convective stratiform algorithm # create empty arrays - ze_bkg = np.zeros(refl.shape, dtype=float) + refl_bkg = np.zeros(refl.shape, dtype=float) conv_core_array = np.zeros(refl.shape, dtype=float) conv_strat_array = np.zeros(refl.shape, dtype=float) mask_array = np.zeros(refl.shape, dtype=float) # fill with missing (nan) - ze_bkg[:] = np.nan + refl_bkg[:] = np.nan conv_core_array[:] = np.nan conv_strat_array[:] = np.nan mask_array[:] = np.nan @@ -101,38 +101,36 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # create background array bkg_mask_array = np.ones((bkgDiameter_pix, bkgDiameter_pix), dtype=float) # mask outside circular region - bkg_mask_array = yuter_convsf.radialDistanceMask_array(bkg_mask_array, minradiuskm=0, maxradiuskm=bkgRad_km, - x_pixsize=dx / 1000, y_pixsize=dy / 1000, - centerx=bkg_center, centery=bkg_center, circular=True) + bkg_mask_array = radialDistanceMask(bkg_mask_array, minradiuskm=0, maxradiuskm=bkgRad_km, x_pixsize=dx / 1000, + y_pixsize=dy / 1000, centerx=bkg_center, centery=bkg_center, circular=True) # Create large mask array for determining where to calculate convective stratiform # initialize array with 1 (calculate convective stratiform over entire array) mask_array[:] = 1 # if True, create radial mask if applyLgRadialMask: - mask_array = yuter_convsf.radialDistanceMask_array(mask_array, lgRadialMask_minRadkm, lgRadialMask_maxRadkm, - x_pixsize=dx/1000, y_pixsize=dy/1000, centerx=int(np.floor(refl.shape[0] / 2)), - centery=int(np.floor(refl.shape[1] / 2)), circular=True) + mask_array = radialDistanceMask(mask_array, lgRadialMask_minRadkm, lgRadialMask_maxRadkm, x_pixsize=dx / 1000, + y_pixsize=dy / 1000, centerx=int(np.floor(refl.shape[0] / 2)), + centery=int(np.floor(refl.shape[1] / 2)), circular=True) #%% Convective stratiform detection # Compute background radius - ze_bkg = yuter_convsf.backgroundIntensity_array(refl, bkg_mask_array, dBaveraging) + refl_bkg = yuter_convsf.backgroundIntensity_array(refl, bkg_mask_array, dBaveraging) # mask background average - ze_bkg = np.ma.masked_where(refl.mask, ze_bkg) + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) # Get convective core array from cosine scheme, or scalar scheme if useCosine: - conv_core_array = yuter_convsf.convcore_cos_scheme_array(refl, ze_bkg, maxDiff, zeroDiffCosValue, alwaysConvThres, - CS_CORE) + conv_core_array = convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE) else: - conv_core_array = yuter_convsf.convcore_scaled_array(refl, ze_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addition) + conv_core_array = convcore_scaled(refl, refl_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addition) # count convective cores corecount = np.count_nonzero(conv_core_array) # Assign convective radii based on background reflectivity - convRadiuskm = yuter_convsf.assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius=dBZforMaxConvRad, maxConvRadius=maxConvRad_km) + convRadiuskm = assignConvRadiuskm(refl_bkg, dBZformaxconvradius=dBZforMaxConvRad, maxConvRadius=maxConvRad_km) # Incorporate convective radius using binary dilation # Create empty array for assignment @@ -156,63 +154,243 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, conv_core_copy[temp_assignment >= 1] = CS_CORE # Now do convective stratiform classification - conv_strat_array = yuter_convsf.classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - mindBZused, weakEchoThres) - - return ze_bkg, conv_core_array, conv_strat_array - - # go through map array once to compute the background intensity and find convective cores - # pixel by pixel method - # for j in np.arange(0, ysize_refl, 1): - # for i in np.arange(0, xsize_refl, 1): - # - # # compute background value - # ze_bkg[j, i] = yuter_convsf.backgroundIntensity(refl, i, j, bkgDiameter_pix, bkg_mask_array, dBaveraging) - # - # # identify convective cores - # if use_cosine: - # conv_core_array[j, i] = yuter_convsf.convcore_cos_scheme(refl[j, i], ze_bkg[j, i], minZeDiff, convThresB, alwaysConvThres, CS_CORE) - - # else: - # mask_array = yuter_convsf.radialDistanceMask_array(mask_array, minRad_km, maxRad_km, - # x_pixsize=dx/1000, y_pixsize=dy/1000, centerx=int(np.floor(xsize_refl / 2)), - # centery=int(np.floor(ysize_refl / 2)), circular=False) - - # initialize mask arrays (1-5 km) - # convmaskarray1 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, centerConvMask_x) - # convmaskarray2 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, centerConvMask_x) - # convmaskarray3 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, centerConvMask_x) - # convmaskarray4 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, centerConvMask_x) - # convmaskarray5 = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, centerConvMask_x) - -# if incorp_rad: -# t1 = time.time() -# # Loop through array for a final time to incorporate the convective radii -# for j in np.arange(0, convRadiuskm.shape[1], 1): -# -# for i in np.arange(0, convRadiuskm.shape[0], 1): -# -# # if point is a convective core, find radius, get convective mask radius and incorporate radius -# if conv_core_array[j, i] == CS_CORE: -# convRadius = np.floor(convRadiuskm[j, i]) -# -# if convRadius == 1: -# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 1, dx / 1000, dy / 1000, -# centerConvMask_x) -# elif convRadius > 1: -# if convRadius == 2: -# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 2, dx / 1000, dy / 1000, -# centerConvMask_x) -# elif convRadius == 3: -# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 3, dx / 1000, dy / 1000, -# centerConvMask_x) -# elif convRadius == 4: -# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 4, dx / 1000, dy / 1000, -# centerConvMask_x) -# elif convRadius == 5: -# conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, 5, dx / 1000, dy / 1000, -# centerConvMask_x) -# -# conv_strat_array = yuter_convsf.incorporateConvRadius(conv_strat_array, i, j, maxConvDiameter, -# conv_mask_array, NOSFCECHO, CONV) \ No newline at end of file + conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + mindBZused, weakEchoThres) + + return refl_bkg, conv_core_array, conv_strat_array + + +def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, + y_pixsize, centerx, centery, circular=True): + """ + Computes a radial distance mask, everything with distance between minradiuskm and maxradiuskm is assigned 1, everything else is assigned 0. This version can handle rectangular arrays and pixels as well as square ones. + + + Parameters + ---------- + mask_array : array + Array to mask + minradiuskm, maxradiuskm : float + The minimum and maximum radius of the non-maked region in kilometers. + x_pixsize, y_pixsize : float + The pixel size in the x- and y-dimension in kilometers, respectively + centerx, centery : int + The center pixel in the x- and y-dimension, respectively + + Returns + ------- + mask_array : array + Rectangular array masked by a radial distance. + """ + + xsize, ysize = mask_array.shape + + for j in np.arange(0, ysize, 1): + for i in np.arange(0, xsize, 1): + # compute range to pixel + if circular: + x_range_sq = ((centerx - i) * x_pixsize) ** 2 + y_range_sq = ((centery - j) * y_pixsize) ** 2 + range = np.sqrt(x_range_sq + y_range_sq) + # if circular is False, use square mask + else: + x_range = abs(int(np.floor(centerx - i) * x_pixsize)) + y_range = abs(int(np.floor(centery - j) * y_pixsize)) + + if x_range > y_range: + range = x_range + else: + range = y_range + # if range is within min and max, set to True + if (range <= maxradiuskm) and (range >= minradiuskm): + mask_array[j, i] = 1 + else: + mask_array[j, i] = 0 + + return mask_array + + +def convcore_cos_scheme(refl, z_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE): + """ + Cosine scheme for determining is convective core + + Parameters + ---------- + zeVal : float + Reflectivity value of point + refl_bkg : float + Reflectivity value of background value + maxDiff : float + Minimum difference between zeVal and refl_bkg needed for convective classification + zeroDiffCosVal : float + Convective threshold used in the cosine function + alwaysConvThres : float + All values above this threshold considered to be convective + + Returns + ------- + is_core : bool + Boolean if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zeDiff for entire array + zDiff = maxDiff * np.cos((np.pi * z_bkg) / (2 * zeroDiffCosVal)) + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[z_bkg < 0] = maxDiff # where background less than zero, set to min diff + + # set values + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[(refl - z_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + + +def convcore_scaled(refl, z_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): + + """ + Cosine scheme for determining is convective core + + Parameters + ---------- + zeVal : float + Reflectivity value of point + z_bkg : float + Reflectivity value of background value + minZediff : float + Minimum difference between zeVal and refl_bkg needed for convective classification + convThresB : float + Convective threshold used in the cosine function + alwaysConvThres : float + All values above this threshold considered to be convective + + Returns + ------- + is_core : bool + Boolean if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zeDiff for entire array + if addition: + zeDiff = maxDiff + z_bkg + else: + zeDiff = maxDiff * z_bkg + zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero + zeDiff[z_bkg < 0] = 0 # where background less than zero, set to zero + + # set values + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + +def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + maxConvDiameter : int + maximum convective diameter in kilometers + radius_km : int + convective radius in kilometers + xpacing, yspacing : float + x- and y-dimension pixel size in meters, respectively + centerConvMask_x : int + index of center point + + Returns + ------- + mean : conv_mask_array + array masked based on distance of convective diameter + """ + + conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) + conv_mask_array = radialDistanceMask(conv_mask_array, 0, radius_km, xspacing, yspacing, centerConvMask_x, + centerConvMask_x, True) + + return conv_mask_array + +def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): + # alternative version for assigning convective radii + # returns array the same size as refl_bkg with values for convective radii + """ + Assigns the convective radius in kilometers based on the background reflectivity + + Parameters + ---------- + refl_bkg : array + array of background reflectivity values + dBZformaxconvradius : float + reflectivity value for maximum convective radius (5 km) + maxConvRadius : float, optional + maximum convective radius in kilometers + + Returns + ------- + convRadiuskm : array + array of convective radii based on background values and dBZ for max. conv radius + """ + + convRadiuskm = np.ones_like(refl_bkg) + + convRadiuskm[refl_bkg >= (dBZformaxconvradius - 15)] = maxConvRadius - 3 + convRadiuskm[refl_bkg >= (dBZformaxconvradius - 10)] = maxConvRadius - 2 + convRadiuskm[refl_bkg >= (dBZformaxconvradius - 5)] = maxConvRadius - 1 + convRadiuskm[refl_bkg >= dBZformaxconvradius] = maxConvRadius + + return convRadiuskm + +def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + refl : array + Array of reflectivity values + conv_strat_array : array + Array with convective stratiform classifications + conv_core_array : array + Array with convective cores + NOSFCECHO : int + Value to assign points classified as no surface echo + CONV : int + Value to assign points classified as convective + SF : int + Value to assign points classified as stratiform + WEAKECHO : int + Value to assign points classfied as weak echo + CS_CORE : int + Value assigned to convective cores in conv_core_array + MINDBZUSE : float + Minimum dBZ value to consider in classification, all values below this will be set to NOSFCECHO + WEAKECHOTHRES : float + dBZ threshold for weak echo classification, all values below this will be set to WEAKECHO + + Returns + ------- + mean : conv_strat_array + conv_strat_array with initial classifications + """ + + # assuming order so that each point is only assigned one time, no overlapping assignment + # initially, assign every point to stratiform + conv_strat_array[:] = SF + # where reflectivity is masked, set to no surface echo + conv_strat_array[refl.mask] = NOSFCECHO + # assign convective cores to CONV + conv_strat_array[conv_core_array==CS_CORE] = CONV + # assign reflectivity less than weakechothres to weak echo + conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO + # assign reflectivity less than minimum to no surface echo + conv_strat_array[refl < MINDBZUSE] = NOSFCECHO + + return conv_strat_array \ No newline at end of file From cfb4d67413618c14a5b3a013327401eb53fdafd9 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 14:59:02 -0400 Subject: [PATCH 11/66] Remove array creation --- pyart/retrieve/_echo_class_updated.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 169ebb47e3..cff3e441f2 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -61,20 +61,6 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, print("Max conv radius must be less than 5 km, exiting") # quit - #%% Set up arrays and values for convective stratiform algorithm - - # create empty arrays - refl_bkg = np.zeros(refl.shape, dtype=float) - conv_core_array = np.zeros(refl.shape, dtype=float) - conv_strat_array = np.zeros(refl.shape, dtype=float) - mask_array = np.zeros(refl.shape, dtype=float) - - # fill with missing (nan) - refl_bkg[:] = np.nan - conv_core_array[:] = np.nan - conv_strat_array[:] = np.nan - mask_array[:] = np.nan - # Constants to fill arrays with CS_CORE = 3 NOSFCECHO = 0 @@ -106,6 +92,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # Create large mask array for determining where to calculate convective stratiform # initialize array with 1 (calculate convective stratiform over entire array) + mask_array = np.zeros(refl.shape, dtype=float) mask_array[:] = 1 # if True, create radial mask if applyLgRadialMask: From 316ce6be660d17da85152b0dade5ebd7c362c7d2 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 15:00:13 -0400 Subject: [PATCH 12/66] Create conv strat array before assigning --- pyart/retrieve/_echo_class_updated.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index cff3e441f2..55aed7fab5 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -141,6 +141,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, conv_core_copy[temp_assignment >= 1] = CS_CORE # Now do convective stratiform classification + conv_strat_array = np.zeros_like(refl) conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, mindBZused, weakEchoThres) From f3ebded48e21720ca7e7d46eac8edbecc7392ba3 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 15:36:24 -0400 Subject: [PATCH 13/66] add background intensity --- pyart/retrieve/_echo_class_updated.py | 44 ++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 55aed7fab5..247c2d6aaf 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -2,8 +2,6 @@ import sys sys.path.append('C:\\Users\\lmtomkin\\Documents\\GitHub\\pyart_convsf\\pyart\\retrieve\\') -import yuter_convsf -import time import scipy.ndimage def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, @@ -103,7 +101,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, #%% Convective stratiform detection # Compute background radius - refl_bkg = yuter_convsf.backgroundIntensity_array(refl, bkg_mask_array, dBaveraging) + refl_bkg = backgroundIntensity(refl, bkg_mask_array, dBaveraging) # mask background average refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) @@ -126,7 +124,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # Loop through radii for radius in np.arange(1, maxConvRad_km+1): # create mask array for radius incorporation - conv_mask_array = yuter_convsf.init_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) + conv_mask_array = init_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) # find location of radius temp = convRadiuskm == radius # get cores for given radius @@ -198,6 +196,44 @@ def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, return mask_array +def backgroundIntensity(refl, bkg_mask_array, dBaveraging): + """ + For a pixel in the array determine the average intensity of the surrounding pixels in the window. + The window is passed in as mask_array + + Parameters + ---------- + refl : array + Reflectivity array to compute average + bkg_mask_array : array + Array of radial points to use for average + dBaveraging : bool + If True, converts dBZ to linear Z before averaging + + Returns + ------- + refl_bkg : array + Array of average values + """ + + # if dBaverage is true, convert reflectivity to linear Z + if dBaveraging: + refl = 10 ** (refl / 10) + + # calculate background reflectivity with circular footprint + refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', + footprint=bkg_mask_array.astype(bool), cval=np.nan) + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + # if dBaveraging is true, convert background reflectivity to dBZ + if dBaveraging: + refl_bkg = 10 * np.log10(refl_bkg) + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + return refl_bkg + def convcore_cos_scheme(refl, z_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE): """ From e0dc6f8cec00848670409a10069f1da6f824e518 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 15:48:02 -0400 Subject: [PATCH 14/66] Adding documentation --- pyart/retrieve/_echo_class_updated.py | 89 +++++++++++++++------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 247c2d6aaf..5e755056bd 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -146,23 +146,27 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, return refl_bkg, conv_core_array, conv_strat_array +# functions def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, y_pixsize, centerx, centery, circular=True): """ - Computes a radial distance mask, everything with distance between minradiuskm and maxradiuskm is assigned 1, everything else is assigned 0. This version can handle rectangular arrays and pixels as well as square ones. - + Computes a radial distance mask, everything with distance between minradiuskm + and maxradiuskm is assigned 1, everything else is assigned 0. This version can + handle rectangular arrays and pixels as well as square ones. Parameters ---------- mask_array : array Array to mask minradiuskm, maxradiuskm : float - The minimum and maximum radius of the non-maked region in kilometers. + The minimum and maximum radius of the non-masked region in kilometers. x_pixsize, y_pixsize : float The pixel size in the x- and y-dimension in kilometers, respectively centerx, centery : int The center pixel in the x- and y-dimension, respectively + circular : bool + True returns circular mask Returns ------- @@ -198,8 +202,8 @@ def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, def backgroundIntensity(refl, bkg_mask_array, dBaveraging): """ - For a pixel in the array determine the average intensity of the surrounding pixels in the window. - The window is passed in as mask_array + Calculate the background of the given refl array. The footprint used to + calculate the average for each pixel is given by bkg_mask_array Parameters ---------- @@ -235,82 +239,88 @@ def backgroundIntensity(refl, bkg_mask_array, dBaveraging): return refl_bkg -def convcore_cos_scheme(refl, z_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE): +def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE): """ - Cosine scheme for determining is convective core + Function for assigning convective cores based on a cosine function Parameters ---------- - zeVal : float - Reflectivity value of point + refl : array + Reflectivity values refl_bkg : float - Reflectivity value of background value + Background average of reflectivity values maxDiff : float - Minimum difference between zeVal and refl_bkg needed for convective classification + Maximum difference between refl and refl_bkg needed for convective classification zeroDiffCosVal : float - Convective threshold used in the cosine function + Value where the cosine function returns a zero difference alwaysConvThres : float All values above this threshold considered to be convective + CS_CORE : int + Value assigned to convective pixels Returns ------- - is_core : bool - Boolean if point is convective (1) or not (0) + conv_core_array : array + Array of booleans if point is convective (1) or not (0) """ # initialize entire array to not a convective core conv_core_array = np.zeros_like(refl) # calculate zeDiff for entire array - zDiff = maxDiff * np.cos((np.pi * z_bkg) / (2 * zeroDiffCosVal)) + zDiff = maxDiff * np.cos((np.pi * refl_bkg) / (2 * zeroDiffCosVal)) zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[z_bkg < 0] = maxDiff # where background less than zero, set to min diff + zDiff[refl_bkg < 0] = maxDiff # where background less than zero, set to min diff # set values conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[(refl - z_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array -def convcore_scaled(refl, z_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): +def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): """ - Cosine scheme for determining is convective core + Function for assigning convective cores based on a scalar difference Parameters ---------- - zeVal : float - Reflectivity value of point - z_bkg : float - Reflectivity value of background value - minZediff : float - Minimum difference between zeVal and refl_bkg needed for convective classification - convThresB : float - Convective threshold used in the cosine function + refl : array + Reflectivity values + refl_bkg : float + Background average of reflectivity values + maxDiff : float + Maximum difference between refl and refl_bkg needed for convective classification alwaysConvThres : float All values above this threshold considered to be convective + CS_CORE : int + Value assigned to convective pixels + addition : bool + Boolean to determine if scalar should be added (True) or multiplied (False) Returns ------- - is_core : bool - Boolean if point is convective (1) or not (0) + conv_core_array : array + Array of booleans if point is convective (1) or not (0) """ # initialize entire array to not a convective core conv_core_array = np.zeros_like(refl) - # calculate zeDiff for entire array + # calculate zDiff for entire array + # if addition, add difference. Else, multiply difference if addition: - zeDiff = maxDiff + z_bkg + zDiff = maxDiff + refl_bkg else: - zeDiff = maxDiff * z_bkg - zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero - zeDiff[z_bkg < 0] = 0 # where background less than zero, set to zero + zDiff = maxDiff * refl_bkg + + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero # set values conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[refl >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core + conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array @@ -331,19 +341,18 @@ def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, center Returns ------- - mean : conv_mask_array + conv_mask_array : array array masked based on distance of convective diameter """ conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) - conv_mask_array = radialDistanceMask(conv_mask_array, 0, radius_km, xspacing, yspacing, centerConvMask_x, - centerConvMask_x, True) + conv_mask_array = radialDistanceMask(conv_mask_array, 0, radius_km, xspacing, yspacing, + centerConvMask_x, centerConvMask_x, True) return conv_mask_array def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): - # alternative version for assigning convective radii - # returns array the same size as refl_bkg with values for convective radii + """ Assigns the convective radius in kilometers based on the background reflectivity From e9242d2060808e763229ae9f7a94b391ced45230 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 15:59:54 -0400 Subject: [PATCH 15/66] documentation --- pyart/retrieve/_echo_class_updated.py | 77 +++++++++++++++------------ 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 5e755056bd..aa0d13ae46 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -1,20 +1,20 @@ import numpy as np import sys + sys.path.append('C:\\Users\\lmtomkin\\Documents\\GitHub\\pyart_convsf\\pyart\\retrieve\\') import scipy.ndimage + def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, useCosine=True, maxDiff=8, zeroDiffCosVal=55, - weakEchoThres=5.0, mindBZused=5.0, scalarDiff=1.5, addition=True, + weakEchoThres=5.0, mindBZused=5.0, dBaveraging=False, applyLgRadialMask=False, lgRadialMask_minRadkm=0, lgRadialMask_maxRadkm=170, - dBZforMaxConvRad=30, maxConvRad_km=5.0, - incorpConvRad=True): - + dBZforMaxConvRad=30, maxConvRad_km=5.0): """ - We perform the Steiner et al. (1995) algorithm for echo classification + We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point as either convective, stratiform or undefined. Grid points are classified as follows, @@ -24,7 +24,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, 2 = Convective 3 = Weak Echo - ref : array + refl : array array of reflectivity values x, y : array x and y coordinates of reflectivity array, respectively @@ -32,26 +32,34 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, The x- and y-dimension resolutions in meters, respectively. alwaysConvThres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective - minZeDiff : float, optional - Minimum difference between background average and reflectivity in order to be classified as convective. - a value in Yuter et al. (2005) - convThresB : float, optional - Convective threshold used in cosine function for classifying convective vs. stratiform - b value in Yuter et al. (2005) - bkg_rad : float, optional + bkgRad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km - maxConvRad_km : float, optional - Maximum radius around convective cores to classify as convective. Default is 5 km + useCosine : bool, optional + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) + maxDiff : float, optional + Maximum difference between background average and reflectivity in order to be classified as convective. + a value in Yuter and Houze (1997) + zeroDiffCosVal : float, optional + Value where difference between background average and reflectivity is zero in the cosine function + b value in Yuter and Houze (1997) + scalarDiff : float, optional + If using a scalar difference scheme, this value is the multiplier or addition to the background average + addition : bool, optional + Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used weakEchoThres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo minDBZused : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo + dBaveraging : bool, optional + True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values applyRadialMask : bool, optional - Flag to set a radial mask for algorithm + Flag to set a large radial mask for algorithm + lgRadialMask_minRadkm, lgRadialMask_maxkm : float, optional + Values for setting the large radial mask dBZforMaxConvRadius : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius - dBaveraging : bool, optional - True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + maxConvRad_km : float, optional + Maximum radius around convective cores to classify as convective. Default is 5 km """ @@ -66,7 +74,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, SF = 1 CONV = 2 - #%% Set up mask arrays + # %% Set up mask arrays # prepare for convective mask arrays # calculate maximum convective diameter from max. convective radius (input) maxConvDiameter = int(np.floor((maxConvRad_km / (dx / 1000)) * 2)) @@ -98,7 +106,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, y_pixsize=dy / 1000, centerx=int(np.floor(refl.shape[0] / 2)), centery=int(np.floor(refl.shape[1] / 2)), circular=True) - #%% Convective stratiform detection + # %% Convective stratiform detection # Compute background radius refl_bkg = backgroundIntensity(refl, bkg_mask_array, dBaveraging) @@ -122,7 +130,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, temp_assignment = np.zeros_like(conv_core_array) # Loop through radii - for radius in np.arange(1, maxConvRad_km+1): + for radius in np.arange(1, maxConvRad_km + 1): # create mask array for radius incorporation conv_mask_array = init_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) # find location of radius @@ -146,6 +154,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, return refl_bkg, conv_core_array, conv_strat_array + # functions def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, @@ -200,6 +209,7 @@ def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, return mask_array + def backgroundIntensity(refl, bkg_mask_array, dBaveraging): """ Calculate the background of the given refl array. The footprint used to @@ -269,18 +279,17 @@ def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres # calculate zeDiff for entire array zDiff = maxDiff * np.cos((np.pi * refl_bkg) / (2 * zeroDiffCosVal)) - zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = maxDiff # where background less than zero, set to min diff + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[refl_bkg < 0] = maxDiff # where background less than zero, set to min diff # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): - """ Function for assigning convective cores based on a scalar difference @@ -315,15 +324,16 @@ def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition= else: zDiff = maxDiff * refl_bkg - zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array + def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): """ Does and initial convective stratiform classification @@ -351,8 +361,8 @@ def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, center return conv_mask_array -def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): +def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): """ Assigns the convective radius in kilometers based on the background reflectivity @@ -380,6 +390,7 @@ def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): return convRadiuskm + def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): """ @@ -420,10 +431,10 @@ def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, # where reflectivity is masked, set to no surface echo conv_strat_array[refl.mask] = NOSFCECHO # assign convective cores to CONV - conv_strat_array[conv_core_array==CS_CORE] = CONV + conv_strat_array[conv_core_array == CS_CORE] = CONV # assign reflectivity less than weakechothres to weak echo conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO # assign reflectivity less than minimum to no surface echo conv_strat_array[refl < MINDBZUSE] = NOSFCECHO - return conv_strat_array \ No newline at end of file + return conv_strat_array From 3b14a23bfd0766370fce732f18df075ee1cd3263 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 16:20:32 -0400 Subject: [PATCH 16/66] Update echo_class.py --- pyart/retrieve/echo_class.py | 133 ++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index e186f8575b..5a72b9bb09 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -107,10 +107,13 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} -def conv_strat(grid, dx=None, dy=None, intense=42.0, - work_level=3000.0, peak_relation='default', - area_relation='medium', bkg_rad=11000.0, - use_intense=True, fill_value=None, +def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, + useCosine=True, maxDiff=8, zeroDiffCosVal=55, + scalarDiff=1.5, addition=True, + weakEchoThres=5.0, mindBZused=5.0, + dBaveraging=False, applyLgRadialMask=False, + lgRadialMask_minRadkm=0, lgRadialMask_maxRadkm=170, + dBZforMaxConvRad=30, maxConvRad_km=5.0, fill_value=None, refl_field=None, estimateFlag=True, estimateOffset=5): """ Partition reflectivity into convective-stratiform using the Yuter @@ -123,30 +126,42 @@ def conv_strat(grid, dx=None, dy=None, intense=42.0, dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values. - intense : float, optional - The intensity value in dBZ. Grid points with a reflectivity - value greater or equal to the intensity are automatically - flagged as convective. See reference for more information. - work_level : float, optional - The working level (separation altitude) in meters. This is the height - at which the partitioning will be done, and should minimize bright band - contamination. See reference for more information. - peak_relation : 'default' or 'sgp', optional - The peakedness relation. See reference for more information. - area_relation : 'small', 'medium', 'large', or 'sgp', optional - The convective area relation. See reference for more information. - bkg_rad : float, optional - The background radius in meters. See reference for more information. - use_intense : bool, optional - True to use the intensity criteria. - fill_value : float, optional - Missing value used to signify bad data points. A value of None - will use the default fill value as defined in the Py-ART - configuration file. - refl_field : str, optional - Field in grid to use as the reflectivity during partitioning. None - will use the default reflectivity field name from the Py-ART - configuration file. + refl : array + array of reflectivity values + x, y : array + x and y coordinates of reflectivity array, respectively + dx, dy : float + The x- and y-dimension resolutions in meters, respectively. + alwaysConvThres : float, optional + Threshold for points that are always convective. All values above the threshold are classifed as convective + bkgRad_km : float, optional + Radius to compute background reflectivity in kilometers. Default is 11 km + useCosine : bool, optional + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) + maxDiff : float, optional + Maximum difference between background average and reflectivity in order to be classified as convective. + a value in Yuter and Houze (1997) + zeroDiffCosVal : float, optional + Value where difference between background average and reflectivity is zero in the cosine function + b value in Yuter and Houze (1997) + scalarDiff : float, optional + If using a scalar difference scheme, this value is the multiplier or addition to the background average + addition : bool, optional + Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + weakEchoThres : float, optional + Threshold for determining weak echo. All values below this threshold will be considered weak echo + minDBZused : float, optional + Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo + dBaveraging : bool, optional + True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + applyRadialMask : bool, optional + Flag to set a large radial mask for algorithm + lgRadialMask_minRadkm, lgRadialMask_maxkm : float, optional + Values for setting the large radial mask + dBZforMaxConvRadius : float, optional + dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius + maxConvRad_km : float, optional + Maximum radius around convective cores to classify as convective. Default is 5 km Returns ------- @@ -183,17 +198,61 @@ def conv_strat(grid, dx=None, dy=None, intense=42.0, ze = np.ma.copy(grid.fields[refl_field]['data']) ze = ze.filled(np.NaN) - convsf_best = _revised_conv_strat(ze, x, y, z) + _, _, convsf_best = _revised_conv_strat(ze, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, + useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, + scalarDiff=scalarDiff, addition=addition, + weakEchoThres=weakEchoThres, mindBZused=mindBZused, + dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + + convsf_dict = {'convsf':{ + 'data': convsf_best, + 'standard_name': 'convsf', + 'long_name': 'Convective stratiform classification', + 'valid_min': 0, + 'valid_max': 3, + 'comment_1': ('Convective-stratiform echo ' + 'classification based on ' + 'Yuter and Houze (1997)'), + 'comment_2': ('0 = Undefined, 1 = Stratiform, ' + '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, x, y, z) - convsf_over = _revised_conv_strat(ze + estimateOffset, x, y, z) + convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, + useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, + scalarDiff=scalarDiff, addition=addition, + weakEchoThres=weakEchoThres, mindBZused=mindBZused, + dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + + convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, + useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, + scalarDiff=scalarDiff, addition=addition, + weakEchoThres=weakEchoThres, mindBZused=mindBZused, + dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + + convsf_dict['convsf_under'] = { + 'data': convsf_under, + 'standard_name': 'convsf_under', + 'long_name': 'Convective stratiform classification (Underestimate)', + 'valid_min': 0, + 'valid_max': 3, + 'comment_1': ('Convective-stratiform echo ' + 'classification based on ' + 'Yuter and Houze (1997)'), + 'comment_2': ('0 = Undefined, 1 = Stratiform, ' + '2 = Convective')} - return {'data': eclass.astype(np.int32), - 'standard_name': 'convsf_classification', - 'long_name': 'Convective stratiform classification', + convsf_dict['convsf_over'] = { + 'data': convsf_under, + 'standard_name': 'convsf_under', + 'long_name': 'Convective stratiform classification (Overestimate)', 'valid_min': 0, - 'valid_max': 2, + 'valid_max': 3, 'comment_1': ('Convective-stratiform echo ' 'classification based on ' 'Yuter and Houze (1997)'), @@ -201,6 +260,10 @@ def conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} + + return convsf_dict + + def hydroclass_semisupervised(radar, mass_centers=None, weights=np.array([1., 1., 1., 0.75, 0.5]), refl_field=None, zdr_field=None, rhv_field=None, From b418d67f8f31fec2d3fbb2fd118314c9f9363108 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 16:20:41 -0400 Subject: [PATCH 17/66] Update _echo_class_updated.py --- pyart/retrieve/_echo_class_updated.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index aa0d13ae46..b9431f3457 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -1,8 +1,4 @@ import numpy as np -import sys - -sys.path.append('C:\\Users\\lmtomkin\\Documents\\GitHub\\pyart_convsf\\pyart\\retrieve\\') - import scipy.ndimage @@ -79,7 +75,8 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # calculate maximum convective diameter from max. convective radius (input) maxConvDiameter = int(np.floor((maxConvRad_km / (dx / 1000)) * 2)) # if diameter is even, make odd - if maxConvDiameter % 2 == 0: maxConvDiameter = maxConvDiameter + 1 + if maxConvDiameter % 2 == 0: + maxConvDiameter = maxConvDiameter + 1 # find center point centerConvMask_x = int(np.floor(maxConvDiameter / 2)) @@ -87,7 +84,8 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # calculate number of pixels for background array given requested background radius and dx bkgDiameter_pix = int(np.floor((bkgRad_km / (dx / 1000)) * 2)) # set diameter to odd if even - if bkgDiameter_pix % 2 == 0: bkgDiameter_pix = bkgDiameter_pix + 1 + if bkgDiameter_pix % 2 == 0: + bkgDiameter_pix = bkgDiameter_pix + 1 # find center point bkg_center = int(np.floor(bkgDiameter_pix / 2)) # create background array @@ -191,18 +189,18 @@ def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, if circular: x_range_sq = ((centerx - i) * x_pixsize) ** 2 y_range_sq = ((centery - j) * y_pixsize) ** 2 - range = np.sqrt(x_range_sq + y_range_sq) + circ_range = np.sqrt(x_range_sq + y_range_sq) # if circular is False, use square mask else: x_range = abs(int(np.floor(centerx - i) * x_pixsize)) y_range = abs(int(np.floor(centery - j) * y_pixsize)) if x_range > y_range: - range = x_range + circ_range = x_range else: - range = y_range + circ_range = y_range # if range is within min and max, set to True - if (range <= maxradiuskm) and (range >= minradiuskm): + if (circ_range <= maxradiuskm) and (circ_range >= minradiuskm): mask_array[j, i] = 1 else: mask_array[j, i] = 0 @@ -257,7 +255,7 @@ def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres ---------- refl : array Reflectivity values - refl_bkg : float + refl_bkg : array Background average of reflectivity values maxDiff : float Maximum difference between refl and refl_bkg needed for convective classification @@ -297,7 +295,7 @@ def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition= ---------- refl : array Reflectivity values - refl_bkg : float + refl_bkg : array Background average of reflectivity values maxDiff : float Maximum difference between refl and refl_bkg needed for convective classification @@ -344,7 +342,7 @@ def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, center maximum convective diameter in kilometers radius_km : int convective radius in kilometers - xpacing, yspacing : float + xspacing, yspacing : float x- and y-dimension pixel size in meters, respectively centerConvMask_x : int index of center point From 330674923803ec602e225c0f3d0bbed0eee1fe17 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 28 Sep 2022 16:24:18 -0400 Subject: [PATCH 18/66] Update echo_class.py --- pyart/retrieve/echo_class.py | 116 +++++++++++++++++------------------ 1 file changed, 57 insertions(+), 59 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 5a72b9bb09..cf7e75722f 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -93,7 +93,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, work_level=work_level, intense=intense, peak_relation=peak_relation, area_relation=area_relation, - use_intense=use_intense,) + use_intense=use_intense, ) return {'data': eclass.astype(np.int32), 'standard_name': 'echo_classification', @@ -126,12 +126,6 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values. - refl : array - array of reflectivity values - x, y : array - x and y coordinates of reflectivity array, respectively - dx, dy : float - The x- and y-dimension resolutions in meters, respectively. alwaysConvThres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkgRad_km : float, optional @@ -150,15 +144,15 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used weakEchoThres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo - minDBZused : float, optional + mindBZused : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo dBaveraging : bool, optional True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values - applyRadialMask : bool, optional + applyLgRadialMask : bool, optional Flag to set a large radial mask for algorithm - lgRadialMask_minRadkm, lgRadialMask_maxkm : float, optional + lgRadialMask_minRadkm, lgRadialMask_maxRadkm : float, optional Values for setting the large radial mask - dBZforMaxConvRadius : float, optional + dBZforMaxConvRad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius maxConvRad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km @@ -199,14 +193,15 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, ze = ze.filled(np.NaN) _, _, convsf_best = _revised_conv_strat(ze, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, - useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, - scalarDiff=scalarDiff, addition=addition, - weakEchoThres=weakEchoThres, mindBZused=mindBZused, - dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, - dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) - - convsf_dict = {'convsf':{ + useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, + scalarDiff=scalarDiff, addition=addition, + weakEchoThres=weakEchoThres, mindBZused=mindBZused, + dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, + lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + + convsf_dict = {'convsf': { 'data': convsf_best, 'standard_name': 'convsf', 'long_name': 'Convective stratiform classification', @@ -219,23 +214,27 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, + convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, + bkgRad_km=bkgRad_km, useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, scalarDiff=scalarDiff, addition=addition, weakEchoThres=weakEchoThres, mindBZused=mindBZused, dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, + lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) - convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, - useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, - scalarDiff=scalarDiff, addition=addition, - weakEchoThres=weakEchoThres, mindBZused=mindBZused, - dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, - dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) - - convsf_dict['convsf_under'] = { + convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, + bkgRad_km=bkgRad_km, + useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, + scalarDiff=scalarDiff, addition=addition, + weakEchoThres=weakEchoThres, mindBZused=mindBZused, + dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, + lgRadialMask_minRadkm=lgRadialMask_minRadkm, + lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, + dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + + convsf_dict['convsf_under'] = { 'data': convsf_under, 'standard_name': 'convsf_under', 'long_name': 'Convective stratiform classification (Underestimate)', @@ -247,7 +246,7 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, 'comment_2': ('0 = Undefined, 1 = Stratiform, ' '2 = Convective')} - convsf_dict['convsf_over'] = { + convsf_dict['convsf_over'] = { 'data': convsf_under, 'standard_name': 'convsf_under', 'long_name': 'Convective stratiform classification (Overestimate)', @@ -259,8 +258,6 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, 'comment_2': ('0 = Undefined, 1 = Stratiform, ' '2 = Convective')} - - return convsf_dict @@ -348,7 +345,7 @@ def hydroclass_semisupervised(radar, mass_centers=None, temp = radar.fields[temp_field]['data'] # convert temp in relative height respect to iso0 - relh = temp*(1000./lapse_rate) + relh = temp * (1000. / lapse_rate) # standardize data refl_std = _standardize(refl, 'Zh') @@ -397,7 +394,7 @@ def _standardize(data, field_name, mx=None, mn=None): """ if field_name == 'relH': - field_std = 2./(1.+np.ma.exp(-0.005*data))-1. + field_std = 2. / (1. + np.ma.exp(-0.005 * data)) - 1. return field_std if (mx is None) or (mn is None): @@ -411,12 +408,12 @@ def _standardize(data, field_name, mx=None, mn=None): if field_name == 'KDP': data[data < -0.5] = -0.5 - data = 10.*np.ma.log10(data+0.6) + data = 10. * np.ma.log10(data + 0.6) elif field_name == 'RhoHV': - data = 10.*np.ma.log10(1.-data) + data = 10. * np.ma.log10(1. - data) mask = np.ma.getmaskarray(data) - field_std = 2.*(data-mn)/(mx-mn)-1. + field_std = 2. * (data - mn) / (mx - mn) - 1. field_std[data < mn] = -1. field_std[data > mx] = 1. field_std[mask] = np.ma.masked @@ -425,7 +422,7 @@ def _standardize(data, field_name, mx=None, mn=None): def _assign_to_class(zh, zdr, kdp, rhohv, relh, mass_centers, - weights=np.array([1., 1., 1., 0.75, 0.5])): + weights=np.array([1., 1., 1., 0.75, 0.5])): """ Assigns an hydrometeor class to a radar range bin computing the distance between the radar variables an a centroid. @@ -466,7 +463,7 @@ def _assign_to_class(zh, zdr, kdp, rhohv, relh, mass_centers, centroids_class.reshape(nvariables, 1, 1), (nvariables, nrays, nbins)) dist[i, :, :] = np.ma.sqrt(np.ma.sum( - ((centroids_class-data)**2.)*weights_mat, axis=0)) + ((centroids_class - data) ** 2.) * weights_mat, axis=0)) # use very large fill_value so that masked entries will be sorted at the # end. There should not be any masked entry anyway @@ -478,7 +475,7 @@ def _assign_to_class(zh, zdr, kdp, rhohv, relh, mass_centers, # Entries with non-valid reflectivity values are set to 0 (No class) mask = np.ma.getmaskarray(zh) - hydroclass = class_vec[0, :, :]+1 + hydroclass = class_vec[0, :, :] + 1 hydroclass[mask] = 0 return hydroclass, min_dist @@ -537,29 +534,29 @@ def _mass_centers_table(): mass_centers_dict = dict() # C-band centroids derived for MeteoSwiss Albis radar # Zh ZDR kdp RhoHV delta_Z - mass_centers_c[0, :] = [13.5829, 0.4063, 0.0497, 0.9868, 1330.3] # DS - mass_centers_c[1, :] = [02.8453, 0.2457, 0.0000, 0.9798, 0653.8] # CR - mass_centers_c[2, :] = [07.6597, 0.2180, 0.0019, 0.9799, -1426.5] # LR - mass_centers_c[3, :] = [31.6815, 0.3926, 0.0828, 0.9978, 0535.3] # GR - mass_centers_c[4, :] = [39.4703, 1.0734, 0.4919, 0.9876, -1036.3] # RN - mass_centers_c[5, :] = [04.8267, -0.5690, 0.0000, 0.9691, 0869.8] # VI - mass_centers_c[6, :] = [30.8613, 0.9819, 0.1998, 0.9845, -0066.1] # WS - mass_centers_c[7, :] = [52.3969, 2.1094, 2.4675, 0.9730, -1550.2] # MH - mass_centers_c[8, :] = [50.6186, -0.0649, 0.0946, 0.9904, 1179.9] # IH/HDG + mass_centers_c[0, :] = [13.5829, 0.4063, 0.0497, 0.9868, 1330.3] # DS + mass_centers_c[1, :] = [02.8453, 0.2457, 0.0000, 0.9798, 0653.8] # CR + mass_centers_c[2, :] = [07.6597, 0.2180, 0.0019, 0.9799, -1426.5] # LR + mass_centers_c[3, :] = [31.6815, 0.3926, 0.0828, 0.9978, 0535.3] # GR + mass_centers_c[4, :] = [39.4703, 1.0734, 0.4919, 0.9876, -1036.3] # RN + mass_centers_c[5, :] = [04.8267, -0.5690, 0.0000, 0.9691, 0869.8] # VI + mass_centers_c[6, :] = [30.8613, 0.9819, 0.1998, 0.9845, -0066.1] # WS + mass_centers_c[7, :] = [52.3969, 2.1094, 2.4675, 0.9730, -1550.2] # MH + mass_centers_c[8, :] = [50.6186, -0.0649, 0.0946, 0.9904, 1179.9] # IH/HDG mass_centers_dict.update({'C': mass_centers_c}) # X-band centroids derived for MeteoSwiss DX50 radar # Zh ZDR kdp RhoHV delta_Z - mass_centers_x[0, :] = [19.0770, 0.4139, 0.0099, 0.9841, 1061.7] # DS - mass_centers_x[1, :] = [03.9877, 0.5040, 0.0000, 0.9642, 0856.6] # CR - mass_centers_x[2, :] = [20.7982, 0.3177, 0.0004, 0.9858, -1375.1] # LR - mass_centers_x[3, :] = [34.7124, -0.3748, 0.0988, 0.9828, 1224.2] # GR - mass_centers_x[4, :] = [33.0134, 0.6614, 0.0819, 0.9802, -1169.8] # RN - mass_centers_x[5, :] = [08.2610, -0.4681, 0.0000, 0.9722, 1100.7] # VI - mass_centers_x[6, :] = [35.1801, 1.2830, 0.1322, 0.9162, -0159.8] # WS - mass_centers_x[7, :] = [52.4539, 2.3714, 1.1120, 0.9382, -1618.5] # MH - mass_centers_x[8, :] = [44.2216, -0.3419, 0.0687, 0.9683, 1272.7] # IH/HDG + mass_centers_x[0, :] = [19.0770, 0.4139, 0.0099, 0.9841, 1061.7] # DS + mass_centers_x[1, :] = [03.9877, 0.5040, 0.0000, 0.9642, 0856.6] # CR + mass_centers_x[2, :] = [20.7982, 0.3177, 0.0004, 0.9858, -1375.1] # LR + mass_centers_x[3, :] = [34.7124, -0.3748, 0.0988, 0.9828, 1224.2] # GR + mass_centers_x[4, :] = [33.0134, 0.6614, 0.0819, 0.9802, -1169.8] # RN + mass_centers_x[5, :] = [08.2610, -0.4681, 0.0000, 0.9722, 1100.7] # VI + mass_centers_x[6, :] = [35.1801, 1.2830, 0.1322, 0.9162, -0159.8] # WS + mass_centers_x[7, :] = [52.4539, 2.3714, 1.1120, 0.9382, -1618.5] # MH + mass_centers_x[8, :] = [44.2216, -0.3419, 0.0687, 0.9683, 1272.7] # IH/HDG mass_centers_dict.update({'X': mass_centers_x}) @@ -611,5 +608,6 @@ def get_freq_band(freq): return None + def yuter_conv_strat(): print('Hello World!') From 630a712b9dcc9f3418249b5f992213e4405d875e Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Thu, 29 Sep 2022 11:45:49 -0400 Subject: [PATCH 19/66] update naming convention --- pyart/retrieve/_echo_class_updated.py | 86 ++++++++++++------------- pyart/retrieve/echo_class.py | 91 +++++++++++++-------------- 2 files changed, 87 insertions(+), 90 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index b9431f3457..0c06217c50 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -2,13 +2,13 @@ import scipy.ndimage -def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, - useCosine=True, maxDiff=8, zeroDiffCosVal=55, - scalarDiff=1.5, addition=True, - weakEchoThres=5.0, mindBZused=5.0, - dBaveraging=False, applyLgRadialMask=False, - lgRadialMask_minRadkm=0, lgRadialMask_maxRadkm=170, - dBZforMaxConvRad=30, maxConvRad_km=5.0): +def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, + use_cosine=True, max_diff=8, zero_diff_cos_val=55, + scalar_diff=1.5, use_addition=True, + weak_echo_thres=5.0, min_dBZ_used=5.0, + dB_averaging=False, apply_lg_rad_mask=False, + lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, + dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point @@ -26,40 +26,40 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, x and y coordinates of reflectivity array, respectively dx, dy : float The x- and y-dimension resolutions in meters, respectively. - alwaysConvThres : float, optional + always_conv_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective - bkgRad_km : float, optional + bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km - useCosine : bool, optional + use_cosine : bool, optional Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) - maxDiff : float, optional + max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. a value in Yuter and Houze (1997) - zeroDiffCosVal : float, optional + zero_diff_cos_val : float, optional Value where difference between background average and reflectivity is zero in the cosine function b value in Yuter and Houze (1997) - scalarDiff : float, optional + scalar_diff : float, optional If using a scalar difference scheme, this value is the multiplier or addition to the background average - addition : bool, optional + use_addition : bool, optional Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used - weakEchoThres : float, optional + weak_echo_thres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo - minDBZused : float, optional + min_dBZ_used : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo - dBaveraging : bool, optional + dB_averaging : bool, optional True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values - applyRadialMask : bool, optional + apply_lg_rad_mask : bool, optional Flag to set a large radial mask for algorithm - lgRadialMask_minRadkm, lgRadialMask_maxkm : float, optional + lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional Values for setting the large radial mask - dBZforMaxConvRadius : float, optional + dBZ_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius - maxConvRad_km : float, optional + max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km """ - if maxConvRad_km > 5: + if max_conv_rad_km > 5: print("Max conv radius must be less than 5 km, exiting") # quit @@ -73,7 +73,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # %% Set up mask arrays # prepare for convective mask arrays # calculate maximum convective diameter from max. convective radius (input) - maxConvDiameter = int(np.floor((maxConvRad_km / (dx / 1000)) * 2)) + maxConvDiameter = int(np.floor((max_conv_rad_km / (dx / 1000)) * 2)) # if diameter is even, make odd if maxConvDiameter % 2 == 0: maxConvDiameter = maxConvDiameter + 1 @@ -82,7 +82,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # prepare background mask array for computing background average # calculate number of pixels for background array given requested background radius and dx - bkgDiameter_pix = int(np.floor((bkgRad_km / (dx / 1000)) * 2)) + bkgDiameter_pix = int(np.floor((bkg_rad_km / (dx / 1000)) * 2)) # set diameter to odd if even if bkgDiameter_pix % 2 == 0: bkgDiameter_pix = bkgDiameter_pix + 1 @@ -91,7 +91,7 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, # create background array bkg_mask_array = np.ones((bkgDiameter_pix, bkgDiameter_pix), dtype=float) # mask outside circular region - bkg_mask_array = radialDistanceMask(bkg_mask_array, minradiuskm=0, maxradiuskm=bkgRad_km, x_pixsize=dx / 1000, + bkg_mask_array = create_radial_mask(bkg_mask_array, minradiuskm=0, maxradiuskm=bkg_rad_km, x_pixsize=dx / 1000, y_pixsize=dy / 1000, centerx=bkg_center, centery=bkg_center, circular=True) # Create large mask array for determining where to calculate convective stratiform @@ -99,38 +99,40 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, mask_array = np.zeros(refl.shape, dtype=float) mask_array[:] = 1 # if True, create radial mask - if applyLgRadialMask: - mask_array = radialDistanceMask(mask_array, lgRadialMask_minRadkm, lgRadialMask_maxRadkm, x_pixsize=dx / 1000, + if apply_lg_rad_mask: + mask_array = create_radial_mask(mask_array, lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km, x_pixsize=dx / 1000, y_pixsize=dy / 1000, centerx=int(np.floor(refl.shape[0] / 2)), centery=int(np.floor(refl.shape[1] / 2)), circular=True) # %% Convective stratiform detection # Compute background radius - refl_bkg = backgroundIntensity(refl, bkg_mask_array, dBaveraging) + refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging) # mask background average refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) # Get convective core array from cosine scheme, or scalar scheme - if useCosine: - conv_core_array = convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE) + if use_cosine: + conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv_thres, CS_CORE) else: - conv_core_array = convcore_scaled(refl, refl_bkg, scalarDiff, alwaysConvThres, CS_CORE, addition=addition) + conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_conv_thres, CS_CORE, + addition=use_addition) # count convective cores corecount = np.count_nonzero(conv_core_array) # Assign convective radii based on background reflectivity - convRadiuskm = assignConvRadiuskm(refl_bkg, dBZformaxconvradius=dBZforMaxConvRad, maxConvRadius=maxConvRad_km) + convRadiuskm = assign_conv_radius_km(refl_bkg, dBZformaxconvradius=dBZ_for_max_conv_rad, + maxConvRadius=max_conv_rad_km) # Incorporate convective radius using binary dilation # Create empty array for assignment temp_assignment = np.zeros_like(conv_core_array) # Loop through radii - for radius in np.arange(1, maxConvRad_km + 1): + for radius in np.arange(1, max_conv_rad_km + 1): # create mask array for radius incorporation - conv_mask_array = init_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) + conv_mask_array = create_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) # find location of radius temp = convRadiuskm == radius # get cores for given radius @@ -148,14 +150,14 @@ def _revised_conv_strat(refl, dx, dy, alwaysConvThres=42, bkgRad_km=11, conv_strat_array = np.zeros_like(refl) conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - mindBZused, weakEchoThres) + min_dBZ_used, weak_echo_thres) return refl_bkg, conv_core_array, conv_strat_array # functions -def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, +def create_radial_mask(mask_array, minradiuskm, maxradiuskm, x_pixsize, y_pixsize, centerx, centery, circular=True): """ Computes a radial distance mask, everything with distance between minradiuskm @@ -208,7 +210,7 @@ def radialDistanceMask(mask_array, minradiuskm, maxradiuskm, x_pixsize, return mask_array -def backgroundIntensity(refl, bkg_mask_array, dBaveraging): +def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): """ Calculate the background of the given refl array. The footprint used to calculate the average for each pixel is given by bkg_mask_array @@ -287,7 +289,7 @@ def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres return conv_core_array -def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): +def convcore_scalar_scheme(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): """ Function for assigning convective cores based on a scalar difference @@ -332,7 +334,7 @@ def convcore_scaled(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition= return conv_core_array -def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): +def create_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): """ Does and initial convective stratiform classification @@ -354,13 +356,13 @@ def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, center """ conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) - conv_mask_array = radialDistanceMask(conv_mask_array, 0, radius_km, xspacing, yspacing, - centerConvMask_x, centerConvMask_x, True) + conv_mask_array = create_radial_mask(conv_mask_array, 0, radius_km, xspacing, yspacing, centerConvMask_x, + centerConvMask_x, True) return conv_mask_array -def assignConvRadiuskm(refl_bkg, dBZformaxconvradius, maxConvRadius=5): +def assign_conv_radius_km(refl_bkg, dBZformaxconvradius, maxConvRadius=5): """ Assigns the convective radius in kilometers based on the background reflectivity diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index cf7e75722f..954ca6a879 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -107,13 +107,13 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} -def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, - useCosine=True, maxDiff=8, zeroDiffCosVal=55, - scalarDiff=1.5, addition=True, - weakEchoThres=5.0, mindBZused=5.0, - dBaveraging=False, applyLgRadialMask=False, - lgRadialMask_minRadkm=0, lgRadialMask_maxRadkm=170, - dBZforMaxConvRad=30, maxConvRad_km=5.0, fill_value=None, +def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, + use_cosine=True, max_diff=8, zero_diff_cos_val=55, + scalar_diff=1.5, use_addition=True, + weak_echo_thres=5.0, min_dBZ_used=5.0, + dB_averaging=False, apply_lg_rad_mask=False, + lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, + dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0, fill_value=None, refl_field=None, estimateFlag=True, estimateOffset=5): """ Partition reflectivity into convective-stratiform using the Yuter @@ -126,35 +126,35 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values. - alwaysConvThres : float, optional + always_conv_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective - bkgRad_km : float, optional + bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km - useCosine : bool, optional + use_cosine : bool, optional Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) - maxDiff : float, optional + max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. a value in Yuter and Houze (1997) - zeroDiffCosVal : float, optional + zero_diff_cos_val : float, optional Value where difference between background average and reflectivity is zero in the cosine function b value in Yuter and Houze (1997) - scalarDiff : float, optional + scalar_diff : float, optional If using a scalar difference scheme, this value is the multiplier or addition to the background average - addition : bool, optional + use_addition : bool, optional Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used - weakEchoThres : float, optional + weak_echo_thres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo - mindBZused : float, optional + min_dBZ_used : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo - dBaveraging : bool, optional + dB_averaging : bool, optional True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values - applyLgRadialMask : bool, optional + apply_lg_rad_mask : bool, optional Flag to set a large radial mask for algorithm - lgRadialMask_minRadkm, lgRadialMask_maxRadkm : float, optional + lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional Values for setting the large radial mask - dBZforMaxConvRad : float, optional + dBZ_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius - maxConvRad_km : float, optional + max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km Returns @@ -192,14 +192,13 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, ze = np.ma.copy(grid.fields[refl_field]['data']) ze = ze.filled(np.NaN) - _, _, convsf_best = _revised_conv_strat(ze, dx, dy, alwaysConvThres=alwaysConvThres, bkgRad_km=bkgRad_km, - useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, - scalarDiff=scalarDiff, addition=addition, - weakEchoThres=weakEchoThres, mindBZused=mindBZused, - dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, - lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, - dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, + use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, + scalar_diff=scalar_diff, use_addition=use_addition, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) convsf_dict = {'convsf': { 'data': convsf_best, @@ -214,25 +213,21 @@ def conv_strat(grid, dx=None, dy=None, alwaysConvThres=42, bkgRad_km=11, '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, - bkgRad_km=bkgRad_km, - useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, - scalarDiff=scalarDiff, addition=addition, - weakEchoThres=weakEchoThres, mindBZused=mindBZused, - dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, - lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, - dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) - - convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, alwaysConvThres=alwaysConvThres, - bkgRad_km=bkgRad_km, - useCosine=useCosine, maxDiff=maxDiff, zeroDiffCosVal=zeroDiffCosVal, - scalarDiff=scalarDiff, addition=addition, - weakEchoThres=weakEchoThres, mindBZused=mindBZused, - dBaveraging=dBaveraging, applyLgRadialMask=applyLgRadialMask, - lgRadialMask_minRadkm=lgRadialMask_minRadkm, - lgRadialMask_maxRadkm=lgRadialMask_maxRadkm, - dBZforMaxConvRad=dBZforMaxConvRad, maxConvRad_km=maxConvRad_km) + convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, + use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, + scalar_diff=scalar_diff, use_addition=use_addition, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + + convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, + use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, + scalar_diff=scalar_diff, use_addition=use_addition, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) convsf_dict['convsf_under'] = { 'data': convsf_under, From c68d31233dad81b9f80ee2d0635e521a3cd88f1f Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Thu, 29 Sep 2022 11:55:27 -0400 Subject: [PATCH 20/66] More variable renaming --- pyart/retrieve/_echo_class_updated.py | 106 +++++++++++++------------- pyart/retrieve/echo_class.py | 48 +++++++----- 2 files changed, 80 insertions(+), 74 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 0c06217c50..d801efc69f 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -8,7 +8,7 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, apply_lg_rad_mask=False, lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, - dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0): + val_for_max_conv_rad=30, max_conv_rad_km=5.0): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point @@ -52,7 +52,7 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, Flag to set a large radial mask for algorithm lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional Values for setting the large radial mask - dBZ_for_max_conv_rad : float, optional + val_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km @@ -91,8 +91,8 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, # create background array bkg_mask_array = np.ones((bkgDiameter_pix, bkgDiameter_pix), dtype=float) # mask outside circular region - bkg_mask_array = create_radial_mask(bkg_mask_array, minradiuskm=0, maxradiuskm=bkg_rad_km, x_pixsize=dx / 1000, - y_pixsize=dy / 1000, centerx=bkg_center, centery=bkg_center, circular=True) + bkg_mask_array = create_radial_mask(bkg_mask_array, min_rad_km=0, max_rad_km=bkg_rad_km, x_pixsize=dx / 1000, + y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) # Create large mask array for determining where to calculate convective stratiform # initialize array with 1 (calculate convective stratiform over entire array) @@ -101,8 +101,8 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, # if True, create radial mask if apply_lg_rad_mask: mask_array = create_radial_mask(mask_array, lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km, x_pixsize=dx / 1000, - y_pixsize=dy / 1000, centerx=int(np.floor(refl.shape[0] / 2)), - centery=int(np.floor(refl.shape[1] / 2)), circular=True) + y_pixsize=dy / 1000, center_x=int(np.floor(refl.shape[0] / 2)), + center_y=int(np.floor(refl.shape[1] / 2)), circular=True) # %% Convective stratiform detection @@ -116,14 +116,14 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv_thres, CS_CORE) else: conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_conv_thres, CS_CORE, - addition=use_addition) + use_addition=use_addition) # count convective cores corecount = np.count_nonzero(conv_core_array) # Assign convective radii based on background reflectivity - convRadiuskm = assign_conv_radius_km(refl_bkg, dBZformaxconvradius=dBZ_for_max_conv_rad, - maxConvRadius=max_conv_rad_km) + convRadiuskm = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad=max_conv_rad_km) # Incorporate convective radius using binary dilation # Create empty array for assignment @@ -157,8 +157,8 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, # functions -def create_radial_mask(mask_array, minradiuskm, maxradiuskm, x_pixsize, - y_pixsize, centerx, centery, circular=True): +def create_radial_mask(mask_array, min_rad_km, max_rad_km, x_pixsize, + y_pixsize, center_x, center_y, circular=True): """ Computes a radial distance mask, everything with distance between minradiuskm and maxradiuskm is assigned 1, everything else is assigned 0. This version can @@ -168,11 +168,11 @@ def create_radial_mask(mask_array, minradiuskm, maxradiuskm, x_pixsize, ---------- mask_array : array Array to mask - minradiuskm, maxradiuskm : float + min_rad_km, max_rad_km : float The minimum and maximum radius of the non-masked region in kilometers. x_pixsize, y_pixsize : float The pixel size in the x- and y-dimension in kilometers, respectively - centerx, centery : int + center_x, center_y : int The center pixel in the x- and y-dimension, respectively circular : bool True returns circular mask @@ -189,20 +189,20 @@ def create_radial_mask(mask_array, minradiuskm, maxradiuskm, x_pixsize, for i in np.arange(0, xsize, 1): # compute range to pixel if circular: - x_range_sq = ((centerx - i) * x_pixsize) ** 2 - y_range_sq = ((centery - j) * y_pixsize) ** 2 + x_range_sq = ((center_x - i) * x_pixsize) ** 2 + y_range_sq = ((center_y - j) * y_pixsize) ** 2 circ_range = np.sqrt(x_range_sq + y_range_sq) # if circular is False, use square mask else: - x_range = abs(int(np.floor(centerx - i) * x_pixsize)) - y_range = abs(int(np.floor(centery - j) * y_pixsize)) + x_range = abs(int(np.floor(center_x - i) * x_pixsize)) + y_range = abs(int(np.floor(center_y - j) * y_pixsize)) if x_range > y_range: circ_range = x_range else: circ_range = y_range # if range is within min and max, set to True - if (circ_range <= maxradiuskm) and (circ_range >= minradiuskm): + if (circ_range <= max_rad_km) and (circ_range >= min_rad_km): mask_array[j, i] = 1 else: mask_array[j, i] = 0 @@ -210,7 +210,7 @@ def create_radial_mask(mask_array, minradiuskm, maxradiuskm, x_pixsize, return mask_array -def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): +def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging): """ Calculate the background of the given refl array. The footprint used to calculate the average for each pixel is given by bkg_mask_array @@ -221,7 +221,7 @@ def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): Reflectivity array to compute average bkg_mask_array : array Array of radial points to use for average - dBaveraging : bool + dB_averaging : bool If True, converts dBZ to linear Z before averaging Returns @@ -231,7 +231,7 @@ def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): """ # if dBaverage is true, convert reflectivity to linear Z - if dBaveraging: + if dB_averaging: refl = 10 ** (refl / 10) # calculate background reflectivity with circular footprint @@ -241,7 +241,7 @@ def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) # if dBaveraging is true, convert background reflectivity to dBZ - if dBaveraging: + if dB_averaging: refl_bkg = 10 * np.log10(refl_bkg) # mask where original reflectivity is invalid refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) @@ -249,7 +249,7 @@ def calc_bkg_intensity(refl, bkg_mask_array, dBaveraging): return refl_bkg -def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres, CS_CORE): +def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv_thres, CS_CORE): """ Function for assigning convective cores based on a cosine function @@ -259,11 +259,11 @@ def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres Reflectivity values refl_bkg : array Background average of reflectivity values - maxDiff : float + max_diff : float Maximum difference between refl and refl_bkg needed for convective classification - zeroDiffCosVal : float + zero_diff_cos_val : float Value where the cosine function returns a zero difference - alwaysConvThres : float + always_conv_thres : float All values above this threshold considered to be convective CS_CORE : int Value assigned to convective pixels @@ -278,18 +278,18 @@ def convcore_cos_scheme(refl, refl_bkg, maxDiff, zeroDiffCosVal, alwaysConvThres conv_core_array = np.zeros_like(refl) # calculate zeDiff for entire array - zDiff = maxDiff * np.cos((np.pi * refl_bkg) / (2 * zeroDiffCosVal)) + zDiff = max_diff * np.cos((np.pi * refl_bkg) / (2 * zero_diff_cos_val)) zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = maxDiff # where background less than zero, set to min diff + zDiff[refl_bkg < 0] = max_diff # where background less than zero, set to min diff # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= always_conv_thres] = CS_CORE # where Z is greater than alwaysConvThres, set to core conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array -def convcore_scalar_scheme(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, addition=False): +def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_conv_thres, CS_CORE, use_addition=False): """ Function for assigning convective cores based on a scalar difference @@ -299,13 +299,13 @@ def convcore_scalar_scheme(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, ad Reflectivity values refl_bkg : array Background average of reflectivity values - maxDiff : float + max_diff : float Maximum difference between refl and refl_bkg needed for convective classification - alwaysConvThres : float + always_conv_thres : float All values above this threshold considered to be convective CS_CORE : int Value assigned to convective pixels - addition : bool + use_addition : bool Boolean to determine if scalar should be added (True) or multiplied (False) Returns @@ -319,34 +319,34 @@ def convcore_scalar_scheme(refl, refl_bkg, maxDiff, alwaysConvThres, CS_CORE, ad # calculate zDiff for entire array # if addition, add difference. Else, multiply difference - if addition: - zDiff = maxDiff + refl_bkg + if use_addition: + zDiff = max_diff + refl_bkg else: - zDiff = maxDiff * refl_bkg + zDiff = max_diff * refl_bkg zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= always_conv_thres] = CS_CORE # where Z is greater than alwaysConvThres, set to core conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array -def create_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): +def create_conv_radius_mask(max_conv_diameter, radius_km, x_spacing, y_spacing, center_conv_mask_x): """ Does and initial convective stratiform classification Parameters ---------- - maxConvDiameter : int + max_conv_diameter : int maximum convective diameter in kilometers radius_km : int convective radius in kilometers - xspacing, yspacing : float + x_spacing, y_spacing : float x- and y-dimension pixel size in meters, respectively - centerConvMask_x : int + center_conv_mask_x : int index of center point Returns @@ -355,14 +355,14 @@ def create_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, cent array masked based on distance of convective diameter """ - conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) - conv_mask_array = create_radial_mask(conv_mask_array, 0, radius_km, xspacing, yspacing, centerConvMask_x, - centerConvMask_x, True) + conv_mask_array = np.zeros((max_conv_diameter, max_conv_diameter)) + conv_mask_array = create_radial_mask(conv_mask_array, 0, radius_km, x_spacing, y_spacing, center_conv_mask_x, + center_conv_mask_x, True) return conv_mask_array -def assign_conv_radius_km(refl_bkg, dBZformaxconvradius, maxConvRadius=5): +def assign_conv_radius_km(refl_bkg, val_for_max_conv_rad, max_conv_rad=5): """ Assigns the convective radius in kilometers based on the background reflectivity @@ -370,9 +370,9 @@ def assign_conv_radius_km(refl_bkg, dBZformaxconvradius, maxConvRadius=5): ---------- refl_bkg : array array of background reflectivity values - dBZformaxconvradius : float + val_for_max_conv_rad : float reflectivity value for maximum convective radius (5 km) - maxConvRadius : float, optional + max_conv_rad : float, optional maximum convective radius in kilometers Returns @@ -383,10 +383,10 @@ def assign_conv_radius_km(refl_bkg, dBZformaxconvradius, maxConvRadius=5): convRadiuskm = np.ones_like(refl_bkg) - convRadiuskm[refl_bkg >= (dBZformaxconvradius - 15)] = maxConvRadius - 3 - convRadiuskm[refl_bkg >= (dBZformaxconvradius - 10)] = maxConvRadius - 2 - convRadiuskm[refl_bkg >= (dBZformaxconvradius - 5)] = maxConvRadius - 1 - convRadiuskm[refl_bkg >= dBZformaxconvradius] = maxConvRadius + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 15)] = max_conv_rad - 3 + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 10)] = max_conv_rad - 2 + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 5)] = max_conv_rad - 1 + convRadiuskm[refl_bkg >= val_for_max_conv_rad] = max_conv_rad return convRadiuskm @@ -421,8 +421,8 @@ def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, Returns ------- - mean : conv_strat_array - conv_strat_array with initial classifications + conv_strat_array : array + array of classifications """ # assuming order so that each point is only assigned one time, no overlapping assignment diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 954ca6a879..1d8f6fc91c 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -193,12 +193,14 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, ze = ze.filled(np.NaN) _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, - use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, - scalar_diff=scalar_diff, use_addition=use_addition, - weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, - dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + use_cosine=use_cosine, max_diff=max_diff, + zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, + use_addition=use_addition, weak_echo_thres=weak_echo_thres, + min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, + apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, + lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) convsf_dict = {'convsf': { 'data': convsf_best, @@ -213,21 +215,25 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, - use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, - scalar_diff=scalar_diff, use_addition=use_addition, - weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, - dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) - - convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, - use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, - scalar_diff=scalar_diff, use_addition=use_addition, - weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, - dBZ_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_conv_thres=always_conv_thres, + bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, + zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, + use_addition=use_addition, weak_echo_thres=weak_echo_thres, + min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, + apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, + lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + + convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_conv_thres=always_conv_thres, + bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, + zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, + use_addition=use_addition, weak_echo_thres=weak_echo_thres, + min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, + apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, + lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) convsf_dict['convsf_under'] = { 'data': convsf_under, From 63d1fd8db4742ac205831edee16893ac4169d949 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Thu, 29 Sep 2022 11:59:15 -0400 Subject: [PATCH 21/66] Add function returns --- pyart/retrieve/_echo_class_updated.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index d801efc69f..38feb821a2 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -57,6 +57,14 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km + Returns + ------- + refl_bkg : array + Array of background values + conv_core_array : array + Array of initial convective cores + conv_strat_array : array + Array of convective stratiform classifcation with convective radii applied """ if max_conv_rad_km > 5: @@ -364,7 +372,7 @@ def create_conv_radius_mask(max_conv_diameter, radius_km, x_spacing, y_spacing, def assign_conv_radius_km(refl_bkg, val_for_max_conv_rad, max_conv_rad=5): """ - Assigns the convective radius in kilometers based on the background reflectivity + Assigns the convective radius in kilometers based on the background values Parameters ---------- @@ -378,7 +386,7 @@ def assign_conv_radius_km(refl_bkg, val_for_max_conv_rad, max_conv_rad=5): Returns ------- convRadiuskm : array - array of convective radii based on background values and dBZ for max. conv radius + array of convective radii based on background values and val for max. conv radius """ convRadiuskm = np.ones_like(refl_bkg) From 3ab5dc02d16f8fe1c6219e7fb8f5eb657a277840 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Thu, 29 Sep 2022 12:00:29 -0400 Subject: [PATCH 22/66] Add function outputs --- pyart/retrieve/echo_class.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 1d8f6fc91c..7c712685d8 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -159,8 +159,8 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, Returns ------- - eclass : dict - Steiner convective-stratiform classification dictionary. + convsf_dict : dict + Convective-stratiform classification dictionary. References ---------- @@ -608,7 +608,3 @@ def get_freq_band(freq): warn('Unknown frequency band') return None - - -def yuter_conv_strat(): - print('Hello World!') From 38e5b0a9b559e664e2261b001dfda5f2e1cfd437 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Thu, 29 Sep 2022 14:47:59 -0400 Subject: [PATCH 23/66] Added threshold for calculating background radius --- pyart/retrieve/_echo_class_updated.py | 21 ++++++++++++++++++--- pyart/retrieve/echo_class.py | 10 ++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 38feb821a2..4c18cf1e76 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -4,7 +4,7 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, - scalar_diff=1.5, use_addition=True, + scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, apply_lg_rad_mask=False, lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, @@ -42,6 +42,8 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, If using a scalar difference scheme, this value is the multiplier or addition to the background average use_addition : bool, optional Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + calc_thres : float, optional + Minimum percentage of points needed to be considered in background average calculation weak_echo_thres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo min_dBZ_used : float, optional @@ -115,7 +117,7 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, # %% Convective stratiform detection # Compute background radius - refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging) + refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres) # mask background average refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) @@ -218,7 +220,7 @@ def create_radial_mask(mask_array, min_rad_km, max_rad_km, x_pixsize, return mask_array -def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging): +def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): """ Calculate the background of the given refl array. The footprint used to calculate the average for each pixel is given by bkg_mask_array @@ -231,6 +233,8 @@ def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging): Array of radial points to use for average dB_averaging : bool If True, converts dBZ to linear Z before averaging + calc_thres : float + Minimum percentage of points needed to be considered in background average calculation Returns ------- @@ -245,6 +249,17 @@ def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging): # calculate background reflectivity with circular footprint refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', footprint=bkg_mask_array.astype(bool), cval=np.nan) + + # if calc_thres is not none, then calculate the number of points used to calculate average + if calc_thres is not None: + # count valid points + refl_count = scipy.ndimage.generic_filter(refl.filled(0), function=np.count_nonzero, mode='constant', + footprint=bkg_mask_array.astype(bool), cval=0) + # find threshold number of points + val = calc_thres * np.count_nonzero(bkg_mask_array) + # mask out values + refl_bkg = np.ma.masked_where(refl_count < val, refl_bkg) + # mask where original reflectivity is invalid refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 7c712685d8..5b3924e8ad 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -109,7 +109,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, - scalar_diff=1.5, use_addition=True, + scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, apply_lg_rad_mask=False, lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, @@ -142,6 +142,8 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, If using a scalar difference scheme, this value is the multiplier or addition to the background average use_addition : bool, optional Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + calc_thres : float, optional + Minimum percentage of points needed to be considered in background average calculation weak_echo_thres : float, optional Threshold for determining weak echo. All values below this threshold will be considered weak echo min_dBZ_used : float, optional @@ -195,7 +197,7 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, weak_echo_thres=weak_echo_thres, + use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, @@ -218,7 +220,7 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, weak_echo_thres=weak_echo_thres, + use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, @@ -228,7 +230,7 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, weak_echo_thres=weak_echo_thres, + use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, From ca99b03466241bc500618574c33803ce509ef505 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 4 Oct 2022 15:22:06 -0400 Subject: [PATCH 24/66] Update __init__.py --- pyart/retrieve/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyart/retrieve/__init__.py b/pyart/retrieve/__init__.py index d91d2ed541..40531e1641 100644 --- a/pyart/retrieve/__init__.py +++ b/pyart/retrieve/__init__.py @@ -4,7 +4,7 @@ """ from .kdp_proc import kdp_maesaka, kdp_schneebeli, kdp_vulpiani -from .echo_class import steiner_conv_strat, hydroclass_semisupervised, yuter_conv_strat +from .echo_class import steiner_conv_strat, conv_strat, hydroclass_semisupervised from .echo_class import get_freq_band from .gate_id import map_profile_to_gates, fetch_radar_time_profile from .simple_moment_calculations import calculate_snr_from_reflectivity From 8c82a0db390d0b5c038587bd1b63a1f2a1125ae7 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 4 Oct 2022 15:22:28 -0400 Subject: [PATCH 25/66] Rename to always_core_threshold --- pyart/retrieve/_echo_class_updated.py | 8 ++++---- pyart/retrieve/echo_class.py | 28 +++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 4c18cf1e76..1d60991597 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -2,7 +2,7 @@ import scipy.ndimage -def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, +def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, @@ -26,7 +26,7 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, x and y coordinates of reflectivity array, respectively dx, dy : float The x- and y-dimension resolutions in meters, respectively. - always_conv_thres : float, optional + always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km @@ -123,9 +123,9 @@ def _revised_conv_strat(refl, dx, dy, always_conv_thres=42, bkg_rad_km=11, # Get convective core array from cosine scheme, or scalar scheme if use_cosine: - conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv_thres, CS_CORE) + conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE) else: - conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_conv_thres, CS_CORE, + conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, CS_CORE, use_addition=use_addition) # count convective cores diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 5b3924e8ad..16acd9ec85 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -107,7 +107,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} -def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, +def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, @@ -126,7 +126,7 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values. - always_conv_thres : float, optional + always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km @@ -194,12 +194,12 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, ze = np.ma.copy(grid.fields[refl_field]['data']) ze = ze.filled(np.NaN) - _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_conv_thres=always_conv_thres, bkg_rad_km=bkg_rad_km, + _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, - min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - apply_lg_rad_mask=apply_lg_rad_mask, + use_addition=use_addition, calc_thres=calc_thres, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) @@ -217,22 +217,22 @@ def conv_strat(grid, dx=None, dy=None, always_conv_thres=42, bkg_rad_km=11, '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_conv_thres=always_conv_thres, + convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, - min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - apply_lg_rad_mask=apply_lg_rad_mask, + use_addition=use_addition, calc_thres=calc_thres, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) - convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_conv_thres=always_conv_thres, + convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, - min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - apply_lg_rad_mask=apply_lg_rad_mask, + use_addition=use_addition, calc_thres=calc_thres, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) From 8486d64afdead70d3fdb34eb1b18e7437b7c7b2e Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 4 Oct 2022 15:22:39 -0400 Subject: [PATCH 26/66] Delete yuter_convsf.py --- pyart/retrieve/yuter_convsf.py | 463 --------------------------------- 1 file changed, 463 deletions(-) delete mode 100644 pyart/retrieve/yuter_convsf.py diff --git a/pyart/retrieve/yuter_convsf.py b/pyart/retrieve/yuter_convsf.py deleted file mode 100644 index 634bd0042b..0000000000 --- a/pyart/retrieve/yuter_convsf.py +++ /dev/null @@ -1,463 +0,0 @@ -import numpy as np -import scipy.ndimage - -def radialDistanceMask_array(mask_array, minradiuskm, maxradiuskm, x_pixsize, - y_pixsize, centerx, centery, circular=True): - """ - Computes a radial distance mask, everything with distance between minradiuskm and maxradiuskm is assigned 1, everything else is assigned 0. This version can handle rectangular arrays and pixels as well as square ones. - - - Parameters - ---------- - mask_array : array - Array to mask - minradiuskm, maxradiuskm : float - The minimum and maximum radius of the non-maked region in kilometers. - x_pixsize, y_pixsize : float - The pixel size in the x- and y-dimension in kilometers, respectively - centerx, centery : int - The center pixel in the x- and y-dimension, respectively - - Returns - ------- - mask_array : array - Rectangular array masked by a radial distance. - """ - - xsize, ysize = mask_array.shape - - for j in np.arange(0, ysize, 1): - for i in np.arange(0, xsize, 1): - # compute range to pixel - if circular: - x_range_sq = ((centerx - i) * x_pixsize) ** 2 - y_range_sq = ((centery - j) * y_pixsize) ** 2 - range = np.sqrt(x_range_sq + y_range_sq) - # if circular is False, use square mask - else: - x_range = abs(int(np.floor(centerx - i) * x_pixsize)) - y_range = abs(int(np.floor(centery - j) * y_pixsize)) - - if x_range > y_range: - range = x_range - else: - range = y_range - # if range is within min and max, set to True - if (range <= maxradiuskm) and (range >= minradiuskm): - mask_array[j, i] = 1 - else: - mask_array[j, i] = 0 - - return mask_array - - -def backgroundIntensity(refl, index_x, index_y, bkgDiameter, mask_array, dBaveraging): - """ - For a pixel in the array determine the average intensity of the surrounding pixels in the window. The window is passed in as mask_array - - Parameters - ---------- - refl : array - Reflectivity array to compute average - index_x, index_y : int - x- and y-dimension index, respectively - bkgDiameter : int - diameter to compute average in pixels - mask_array : array - Array of radial points to use for average - dBaveraging : bool - If True, converts dBZ to linear Z before averaging - - Returns - ------- - mean : float - The average value for the given index - """ - - running_total = 0 - mean = np.nan - pixelradius = 0 - numpixels = 0 - minptsAver = 1 - aval = 0 - centerval = 0 - maski = 0 - maskj = 0 - - xsize, ysize = refl.shape - # only compute background value if there is data at the point - centerval = refl[index_y, index_x] - if np.ma.is_masked(centerval): - return mean - - pixelradius = int(np.floor(bkgDiameter / 2)) - - # note the window will only be centered on point if windim is odd - if pixelradius == (bkgDiameter / 2.0): - print('Warning: windim = {0} is EVEN, background window will not be centered on point\n'.format(bkgDiameter)) - - if (index_x >= 0) and (index_x < xsize) and (index_y >= 0) and (index_y < ysize): - - # loop through point in background window - for j in np.arange(index_y - pixelradius, index_y + pixelradius + 1, 1): - maski = 0 - for i in np.arange(index_x - pixelradius, index_x + pixelradius + 1, 1): - # check that point is within bounds and activated in mask - if (i >= 0) and (i < xsize) and (j >= 0) and (j < ysize) and mask_array[maskj, maski] == 1: - aval = refl[j, i] - - if not np.ma.is_masked(aval): - numpixels += 1 - # converting to linear Z - if dBaveraging: - running_total += 10 ** (aval / 10) - else: - running_total += aval - maski += 1 - maskj += 1 - - if numpixels >= minptsAver: - mean = running_total / numpixels - # converting to dBZ - if dBaveraging: - mean = 10 * np.log10(mean) - - return mean - -def backgroundIntensity_array(refl, mask_array, dBaveraging): - """ - For a pixel in the array determine the average intensity of the surrounding pixels in the window. - The window is passed in as mask_array - - Parameters - ---------- - refl : array - Reflectivity array to compute average - mask_array : array - Array of radial points to use for average - dBaveraging : bool - If True, converts dBZ to linear Z before averaging - - Returns - ------- - refl_bkg : array - Array of average values - """ - - # if dBaverage is true, convert reflectivity to linear Z - if dBaveraging: - refl = 10 ** (refl / 10) - - # calculate background reflectivity with circular footprint - refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', - footprint=mask_array.astype(bool), cval=np.nan) - # mask where original reflectivity is invalid - refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) - - # if dBaveraging is true, convert background reflectivity to dBZ - if dBaveraging: - refl_bkg = 10 * np.log10(refl_bkg) - # mask where original reflectivity is invalid - refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) - - return refl_bkg - -def convcore_cos_scheme(zeVal, ze_bkg, minZeDiff, convThresB, alwaysConvThres, CS_CORE): - # otherconvthres = absconvthres - # intense = truncZconvthres - """ - Cosine scheme for determining is convective core - - Parameters - ---------- - zeVal : float - Reflectivity value of point - ze_bkg : float - Reflectivity value of background value - minZediff : float - Minimum difference between zeVal and ze_bkg needed for convective classification - convThresB : float - Convective threshold used in the cosine function - alwaysConvThres : float - All values above this threshold considered to be convective - - Returns - ------- - is_core : bool - Boolean if point is convective (1) or not (0) - """ - - # initialize to not a convective core - is_core = 0 - - # if zval is greater than or equal to intense, set to core - if zeVal >= alwaysConvThres: - is_core = CS_CORE - elif (not np.ma.is_masked(zeVal)) and (not np.ma.is_masked(ze_bkg)): - # if background is less than zero, set difference to min difference - if ze_bkg < 0: - zeDiff = minZeDiff - # else, use function from Yuter et al. (1997) - else: - zeDiff = minZeDiff * np.cos((np.pi * ze_bkg) / (2 * convThresB)) - # if difference is less than zero, set to zero - if zeDiff < 0: - zeDiff = 0 - # if value minus background is greater than or equal to difference, set to core - if (zeVal - ze_bkg) >= zeDiff: - is_core = CS_CORE - - return is_core - -def convcore_cos_scheme_array(refl, ze_bkg, minZeDiff, convThresB, alwaysConvThres, CS_CORE): - # otherconvthres = absconvthres - # intense = truncZconvthres - """ - Cosine scheme for determining is convective core - - Parameters - ---------- - zeVal : float - Reflectivity value of point - ze_bkg : float - Reflectivity value of background value - minZediff : float - Minimum difference between zeVal and ze_bkg needed for convective classification - convThresB : float - Convective threshold used in the cosine function - alwaysConvThres : float - All values above this threshold considered to be convective - - Returns - ------- - is_core : bool - Boolean if point is convective (1) or not (0) - """ - - # initialize entire array to not a convective core - conv_core_array = np.zeros_like(refl) - - # calculate zeDiff for entire array - zeDiff = minZeDiff * np.cos((np.pi * ze_bkg) / (2 * convThresB)) - zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero - zeDiff[ze_bkg < 0] = minZeDiff # where background less than zero, set to min diff - - # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[(refl - ze_bkg) >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core - - return conv_core_array - -def convcore_scaled_array(refl, ze_bkg, minZeFactor, alwaysConvThres, CS_CORE, addition=False): - # otherconvthres = absconvthres - # intense = truncZconvthres - """ - Cosine scheme for determining is convective core - - Parameters - ---------- - zeVal : float - Reflectivity value of point - ze_bkg : float - Reflectivity value of background value - minZediff : float - Minimum difference between zeVal and ze_bkg needed for convective classification - convThresB : float - Convective threshold used in the cosine function - alwaysConvThres : float - All values above this threshold considered to be convective - - Returns - ------- - is_core : bool - Boolean if point is convective (1) or not (0) - """ - - # initialize entire array to not a convective core - conv_core_array = np.zeros_like(refl) - - # calculate zeDiff for entire array - if addition: - zeDiff = minZeFactor + ze_bkg - else: - zeDiff = minZeFactor * ze_bkg - zeDiff[zeDiff < 0] = 0 # where difference less than zero, set to zero - zeDiff[ze_bkg < 0] = 0 # where background less than zero, set to zero - - # set values - conv_core_array[refl >= alwaysConvThres] = CS_CORE # where Z is greater than alwaysConvThres, set to core - conv_core_array[refl >= zeDiff] = CS_CORE # where difference exceeeds minimum, set to core - - return conv_core_array - -def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): - """ - Does and initial convective stratiform classification - - Parameters - ---------- - refl : array - Array of reflectivity values - conv_strat_array : array - Array with convective stratiform classifications - conv_core_array : array - Array with convective cores - NOSFCECHO : int - Value to assign points classified as no surface echo - CONV : int - Value to assign points classified as convective - SF : int - Value to assign points classified as stratiform - WEAKECHO : int - Value to assign points classfied as weak echo - CS_CORE : int - Value assigned to convective cores in conv_core_array - MINDBZUSE : float - Minimum dBZ value to consider in classification, all values below this will be set to NOSFCECHO - WEAKECHOTHRES : float - dBZ threshold for weak echo classification, all values below this will be set to WEAKECHO - - Returns - ------- - mean : conv_strat_array - conv_strat_array with initial classifications - """ - - # assuming order so that each point is only assigned one time, no overlapping assignment - # initially, assign every point to stratiform - conv_strat_array[:] = SF - # where reflectivity is masked, set to no surface echo - conv_strat_array[refl.mask] = NOSFCECHO - # assign convective cores to CONV - conv_strat_array[conv_core_array==CS_CORE] = CONV - # assign reflectivity less than weakechothres to weak echo - conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO - # assign reflectivity less than minimum to no surface echo - conv_strat_array[refl < MINDBZUSE] = NOSFCECHO - - return conv_strat_array - -def init_conv_radius_mask(maxConvDiameter, radius_km, xspacing, yspacing, centerConvMask_x): - """ - Does and initial convective stratiform classification - - Parameters - ---------- - maxConvDiameter : int - maximum convective diameter in kilometers - radius_km : int - convective radius in kilometers - xpacing, yspacing : float - x- and y-dimension pixel size in meters, respectively - centerConvMask_x : int - index of center point - - Returns - ------- - mean : conv_mask_array - array masked based on distance of convective diameter - """ - - conv_mask_array = np.zeros((maxConvDiameter, maxConvDiameter)) - conv_mask_array = radialDistanceMask_array(conv_mask_array, 0, radius_km, xspacing, yspacing, - centerConvMask_x, centerConvMask_x, True) - - return conv_mask_array - -def assignConvRadiuskm_array(ze_bkg, dBZformaxconvradius, maxConvRadius=5): - # alternative version for assigning convective radii - # returns array the same size as ze_bkg with values for convective radii - """ - Assigns the convective radius in kilometers based on the background reflectivity - - Parameters - ---------- - ze_bkg : array - array of background reflectivity values - dBZformaxconvradius : float - reflectivity value for maximum convective radius (5 km) - maxConvRadius : float, optional - maximum convective radius in kilometers - - Returns - ------- - convRadiuskm : array - array of convective radii based on background values and dBZ for max. conv radius - """ - - convRadiuskm = np.ones_like(ze_bkg) - - convRadiuskm[ze_bkg >= (dBZformaxconvradius - 15)] = maxConvRadius - 3 - convRadiuskm[ze_bkg >= (dBZformaxconvradius - 10)] = maxConvRadius - 2 - convRadiuskm[ze_bkg >= (dBZformaxconvradius - 5)] = maxConvRadius - 1 - convRadiuskm[ze_bkg >= dBZformaxconvradius] = maxConvRadius - - return convRadiuskm - - -def incorporateConvRadius(conv_strat_array, index_x, index_y, maxConvDiameter, conv_mask_array, NOSFCECHO, CONV): - """ - Assigns the area within the convective radius around the convective core as convective - function similar to backgroundIntensity except assigns value to mask array points in conv_strat_array - only passing in legit convcore points to less error checking - should only be called if convradius > 1 - - Parameters - ---------- - conv_strat_array : array - Array with convective stratiform classifications - index_x, index_y : int - x- and y-dimension index, respectfully - maxConvDiameter : int - diameter to assign convective - conv_mask_array : array - Masked array of conv_strat_array - - Returns - ------- - mean : conv_strat_array - conv_strat_array with convective radii applied - """ - - xsize, ysize = conv_strat_array.shape - - maski = 0 - maskj = 0 - - # note the convradius is always odd as it includes the conv core pixel at the center - pixelradius = int(np.floor(maxConvDiameter / 2)) - - # check limits of requested point are within array bounds - if (index_x >= 0) and (index_x < xsize) and (index_y >= 0) and (index_y < ysize): - - # loop through the points in the square window in full size map - for j in np.arange(index_y - pixelradius, index_y + pixelradius + 1, 1): - maski = 0 - for i in np.arange(index_x - pixelradius, index_x + pixelradius + 1, 1): - # check that point is within bounds, activated in mask, and has echo - if (i >= 0) and (i < xsize) and (j >= 0) and (j < ysize) and \ - conv_mask_array[maskj, maski] == 1 and conv_strat_array[j, i] != NOSFCECHO: - # assign pixel as convective - conv_strat_array[j,i] = CONV - maski += 1 - maskj += 1 - - return conv_strat_array - - -# def assignConvRadiuskm(ze_bkg, dBZformaxconvradius, maxconvradius=5): -# # version for single background points -# # returns single convective radii for given background value -# if ze_bkg >= dBZformaxconvradius: -# convRadiuskm = maxconvradius -# elif ze_bkg >= (dBZformaxconvradius - 5): -# convRadiuskm = maxconvradius - 1 -# elif ze_bkg >= (dBZformaxconvradius - 10): -# convRadiuskm = maxconvradius - 2 -# elif ze_bkg >= (dBZformaxconvradius - 15): -# convRadiuskm = maxconvradius - 3 -# else: -# convRadiuskm = maxconvradius - 4 -# -# return convRadiuskm From 4ca1e3eef315ff814563a1315831169b36942b81 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 11 Oct 2022 15:33:30 -0400 Subject: [PATCH 27/66] Placeholders for other function outputs --- pyart/retrieve/echo_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 16acd9ec85..3f0697dc46 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -217,7 +217,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, '2 = Convective')}} if estimateFlag: - convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_core_thres=always_core_thres, + _, _, convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, @@ -227,7 +227,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) - convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_core_thres=always_core_thres, + _, _, convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, From 8e4077bb91d21bcdeb7e0e44d3afce65c4080a85 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 12 Oct 2022 14:29:59 -0400 Subject: [PATCH 28/66] Updated documentation --- pyart/retrieve/_echo_class_updated.py | 57 ++++++++---------- pyart/retrieve/echo_class.py | 87 +++++++++++++++++---------- 2 files changed, 80 insertions(+), 64 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 1d60991597..c974a240bd 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -34,10 +34,10 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. - a value in Yuter and Houze (1997) + "a" value in Eqn. B1 in Yuter and Houze (1997) zero_diff_cos_val : float, optional Value where difference between background average and reflectivity is zero in the cosine function - b value in Yuter and Houze (1997) + "b" value in Eqn. B1 in Yuter and Houze (1997) scalar_diff : float, optional If using a scalar difference scheme, this value is the multiplier or addition to the background average use_addition : bool, optional @@ -64,15 +64,11 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, refl_bkg : array Array of background values conv_core_array : array - Array of initial convective cores + Array of initial convective cores (identified convective elements without convective radii applied) conv_strat_array : array Array of convective stratiform classifcation with convective radii applied """ - if max_conv_rad_km > 5: - print("Max conv radius must be less than 5 km, exiting") - # quit - # Constants to fill arrays with CS_CORE = 3 NOSFCECHO = 0 @@ -80,26 +76,26 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, SF = 1 CONV = 2 - # %% Set up mask arrays + # %% Set up mask arrays for background average and # prepare for convective mask arrays # calculate maximum convective diameter from max. convective radius (input) - maxConvDiameter = int(np.floor((max_conv_rad_km / (dx / 1000)) * 2)) + max_conv_diameter = int(np.floor((max_conv_rad_km / (dx / 1000)) * 2)) # if diameter is even, make odd - if maxConvDiameter % 2 == 0: - maxConvDiameter = maxConvDiameter + 1 + if max_conv_diameter % 2 == 0: + max_conv_diameter = max_conv_diameter + 1 # find center point - centerConvMask_x = int(np.floor(maxConvDiameter / 2)) + center_conv_mask_x = int(np.floor(max_conv_diameter / 2)) # prepare background mask array for computing background average # calculate number of pixels for background array given requested background radius and dx - bkgDiameter_pix = int(np.floor((bkg_rad_km / (dx / 1000)) * 2)) + bkg_diameter_pix = int(np.floor((bkg_rad_km / (dx / 1000)) * 2)) # set diameter to odd if even - if bkgDiameter_pix % 2 == 0: - bkgDiameter_pix = bkgDiameter_pix + 1 + if bkg_diameter_pix % 2 == 0: + bkg_diameter_pix = bkg_diameter_pix + 1 # find center point - bkg_center = int(np.floor(bkgDiameter_pix / 2)) + bkg_center = int(np.floor(bkg_diameter_pix / 2)) # create background array - bkg_mask_array = np.ones((bkgDiameter_pix, bkgDiameter_pix), dtype=float) + bkg_mask_array = np.ones((bkg_diameter_pix, bkg_diameter_pix), dtype=float) # mask outside circular region bkg_mask_array = create_radial_mask(bkg_mask_array, min_rad_km=0, max_rad_km=bkg_rad_km, x_pixsize=dx / 1000, y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) @@ -128,12 +124,9 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, CS_CORE, use_addition=use_addition) - # count convective cores - corecount = np.count_nonzero(conv_core_array) - # Assign convective radii based on background reflectivity - convRadiuskm = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad=max_conv_rad_km) + conv_radius_km = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad=max_conv_rad_km) # Incorporate convective radius using binary dilation # Create empty array for assignment @@ -142,9 +135,9 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, # Loop through radii for radius in np.arange(1, max_conv_rad_km + 1): # create mask array for radius incorporation - conv_mask_array = create_conv_radius_mask(maxConvDiameter, radius, dx / 1000, dy / 1000, centerConvMask_x) + conv_mask_array = create_conv_radius_mask(max_conv_diameter, radius, dx / 1000, dy / 1000, center_conv_mask_x) # find location of radius - temp = convRadiuskm == radius + temp = conv_radius_km == radius # get cores for given radius temp_core = np.ma.masked_where(~temp, conv_core_array) # dilate cores @@ -185,7 +178,7 @@ def create_radial_mask(mask_array, min_rad_km, max_rad_km, x_pixsize, center_x, center_y : int The center pixel in the x- and y-dimension, respectively circular : bool - True returns circular mask + True returns circular mask, False returns a rectangular mask. Returns ------- @@ -272,7 +265,7 @@ def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): return refl_bkg -def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv_thres, CS_CORE): +def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE): """ Function for assigning convective cores based on a cosine function @@ -286,7 +279,7 @@ def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv Maximum difference between refl and refl_bkg needed for convective classification zero_diff_cos_val : float Value where the cosine function returns a zero difference - always_conv_thres : float + always_core_thres : float All values above this threshold considered to be convective CS_CORE : int Value assigned to convective pixels @@ -303,16 +296,16 @@ def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_conv # calculate zeDiff for entire array zDiff = max_diff * np.cos((np.pi * refl_bkg) / (2 * zero_diff_cos_val)) zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = max_diff # where background less than zero, set to min diff + zDiff[refl_bkg < 0] = max_diff # where background less than zero, set to max. diff # set values - conv_core_array[refl >= always_conv_thres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array -def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_conv_thres, CS_CORE, use_addition=False): +def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_core_thres, CS_CORE, use_addition=False): """ Function for assigning convective cores based on a scalar difference @@ -324,7 +317,7 @@ def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_conv_thres, CS_CORE, Background average of reflectivity values max_diff : float Maximum difference between refl and refl_bkg needed for convective classification - always_conv_thres : float + always_core_thres : float All values above this threshold considered to be convective CS_CORE : int Value assigned to convective pixels @@ -351,7 +344,7 @@ def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_conv_thres, CS_CORE, zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero # set values - conv_core_array[refl >= always_conv_thres] = CS_CORE # where Z is greater than alwaysConvThres, set to core + conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core return conv_core_array diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 3f0697dc46..5b74acc6be 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -114,7 +114,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, dB_averaging=False, apply_lg_rad_mask=False, lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0, fill_value=None, - refl_field=None, estimateFlag=True, estimateOffset=5): + refl_field=None, estimate_flag=True, estimate_offset=5): """ Partition reflectivity into convective-stratiform using the Yuter and Houze (1997) algorithm. @@ -125,19 +125,19 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, Grid containing reflectivity field to partition. dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None - the resolution is determined from the first two axes values. + the resolution is determined from the first two axes values parsed from grid object. always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km use_cosine : bool, optional - Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) + Boolean used to determine if a cosine scheme (see Yuter and Houze (1997)) should be used for identifying convective cores (True) or if a simpler scalar scheme (False) should be used. max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. - a value in Yuter and Houze (1997) + "a" value in Eqn. B1 in Yuter and Houze (1997) zero_diff_cos_val : float, optional Value where difference between background average and reflectivity is zero in the cosine function - b value in Yuter and Houze (1997) + "b" value in Eqn. B1 in Yuter and Houze (1997) scalar_diff : float, optional If using a scalar difference scheme, this value is the multiplier or addition to the background average use_addition : bool, optional @@ -149,7 +149,8 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, min_dBZ_used : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo dB_averaging : bool, optional - True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + True if using dBZ reflectivity values that need to be converted to linear Z before averaging. False for + other non-dBZ values (i.e. snow rate) apply_lg_rad_mask : bool, optional Flag to set a large radial mask for algorithm lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional @@ -158,6 +159,18 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km + fill_value : float, optional + Missing value used to signify bad data points. A value of None will use the default fill value as + defined in the Py-ART configuration file. + refl_field : str, optional + Field in grid to use as the reflectivity during partitioning. None will use the default reflectivity + field name from the Py-ART configuration file. + estimate_flag : bool, optional + Determines if over/underestimation should be applied. If true, the algorithm will also be run on the same field + wih the estimate_offset added and the same field with the estimate_offset subtracted. + Default is True (recommended) + estimate_offset : float, optional + Value used to offset the reflectivity values by for the over/underestimation application. Default value is 5 dBZ. Returns ------- @@ -166,12 +179,18 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, References ---------- - Steiner, M. R., R. A. Houze Jr., and S. E. Yuter, 1995: Climatological - Characterization of Three-Dimensional Storm Structure from Operational - Radar and Rain Gauge Data. J. Appl. Meteor., 34, 1978-2007. + Yuter, S. E., and R. A. Houze, Jr., 1997: Measurements of raindrop size + distributions over the Pacific warm pool and implications for Z-R relations. + J. Appl. Meteor., 36, 847-867. """ - # Get fill value + + # Maxmimum convective radius must be less than 5 km + if max_conv_rad_km > 5: + print("Max conv radius must be less than 5 km, exiting") + raise + + # Get fill value for reflectivity field if fill_value is None: fill_value = get_fillvalue() @@ -179,7 +198,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, if refl_field is None: refl_field = get_field_name('reflectivity') - # parse dx and dy + # parse dx and dy if None if dx is None: dx = grid.x['data'][1] - grid.x['data'][0] if dy is None: @@ -194,6 +213,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, ze = np.ma.copy(grid.fields[refl_field]['data']) ze = ze.filled(np.NaN) + # run convective stratiform algorithm _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, @@ -204,6 +224,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + # put data into a dictionary to be added as a field convsf_dict = {'convsf': { 'data': convsf_best, 'standard_name': 'convsf', @@ -216,27 +237,29 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, 'comment_2': ('0 = Undefined, 1 = Stratiform, ' '2 = Convective')}} - if estimateFlag: - _, _, convsf_under = _revised_conv_strat(ze - estimateOffset, dx, dy, always_core_thres=always_core_thres, - bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, - zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, calc_thres=calc_thres, - weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, - lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, - val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) - - _, _, convsf_over = _revised_conv_strat(ze + estimateOffset, dx, dy, always_core_thres=always_core_thres, - bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, - zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, - use_addition=use_addition, calc_thres=calc_thres, - weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, - lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, - val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) - + # If estimation is True, run the algorithm on the field with offset subtracted and the field with the offset added + if estimate_flag: + _, _, convsf_under = _revised_conv_strat(ze - estimate_offset, dx, dy, always_core_thres=always_core_thres, + bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, + zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, + use_addition=use_addition, calc_thres=calc_thres, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, + lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + + _, _, convsf_over = _revised_conv_strat(ze + estimate_offset, dx, dy, always_core_thres=always_core_thres, + bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, + zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, + use_addition=use_addition, calc_thres=calc_thres, + weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, + dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, + lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, + lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + + # save into dictionaries convsf_dict['convsf_under'] = { 'data': convsf_under, 'standard_name': 'convsf_under', From 070eb9fe0dbaba8ab56815ad505bf1efd6509f3e Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 14 Oct 2022 15:26:36 -0400 Subject: [PATCH 29/66] Remove large radial mask --- pyart/retrieve/_echo_class_updated.py | 18 +----------------- pyart/retrieve/echo_class.py | 22 +++++----------------- 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index c974a240bd..67a5469616 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -6,9 +6,7 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, - dB_averaging=False, apply_lg_rad_mask=False, - lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, - val_for_max_conv_rad=30, max_conv_rad_km=5.0): + dB_averaging=False, val_for_max_conv_rad=30, max_conv_rad_km=5.0): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point @@ -50,10 +48,6 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo dB_averaging : bool, optional True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values - apply_lg_rad_mask : bool, optional - Flag to set a large radial mask for algorithm - lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional - Values for setting the large radial mask val_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional @@ -100,16 +94,6 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, bkg_mask_array = create_radial_mask(bkg_mask_array, min_rad_km=0, max_rad_km=bkg_rad_km, x_pixsize=dx / 1000, y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) - # Create large mask array for determining where to calculate convective stratiform - # initialize array with 1 (calculate convective stratiform over entire array) - mask_array = np.zeros(refl.shape, dtype=float) - mask_array[:] = 1 - # if True, create radial mask - if apply_lg_rad_mask: - mask_array = create_radial_mask(mask_array, lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km, x_pixsize=dx / 1000, - y_pixsize=dy / 1000, center_x=int(np.floor(refl.shape[0] / 2)), - center_y=int(np.floor(refl.shape[1] / 2)), circular=True) - # %% Convective stratiform detection # Compute background radius diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 5b74acc6be..2a53fad6b7 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -111,10 +111,8 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, - dB_averaging=False, apply_lg_rad_mask=False, - lg_rad_mask_min_rad_km=0, lg_rad_mask_max_rad_km=170, - dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0, fill_value=None, - refl_field=None, estimate_flag=True, estimate_offset=5): + dB_averaging=False, dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0, + fill_value=None, refl_field=None, estimate_flag=True, estimate_offset=5): """ Partition reflectivity into convective-stratiform using the Yuter and Houze (1997) algorithm. @@ -151,10 +149,6 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, dB_averaging : bool, optional True if using dBZ reflectivity values that need to be converted to linear Z before averaging. False for other non-dBZ values (i.e. snow rate) - apply_lg_rad_mask : bool, optional - Flag to set a large radial mask for algorithm - lg_rad_mask_min_rad_km, lg_rad_mask_max_rad_km : float, optional - Values for setting the large radial mask dBZ_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional @@ -219,9 +213,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, - lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dB_averaging=dB_averaging, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) # put data into a dictionary to be added as a field @@ -244,9 +236,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, - lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dB_averaging=dB_averaging, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) _, _, convsf_over = _revised_conv_strat(ze + estimate_offset, dx, dy, always_core_thres=always_core_thres, @@ -254,9 +244,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, apply_lg_rad_mask=apply_lg_rad_mask, - lg_rad_mask_min_rad_km=lg_rad_mask_min_rad_km, - lg_rad_mask_max_rad_km=lg_rad_mask_max_rad_km, + dB_averaging=dB_averaging, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) # save into dictionaries From 549c45de0de14b2cacd6cd4249e0399adc251995 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 14 Oct 2022 15:40:50 -0400 Subject: [PATCH 30/66] Add other reference --- pyart/retrieve/echo_class.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 2a53fad6b7..d8ecdc226b 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -126,6 +126,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, the resolution is determined from the first two axes values parsed from grid object. always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective + See Yuter et al. (2005) for more detail. bkg_rad_km : float, optional Radius to compute background reflectivity in kilometers. Default is 11 km use_cosine : bool, optional @@ -146,6 +147,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, Threshold for determining weak echo. All values below this threshold will be considered weak echo min_dBZ_used : float, optional Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo + See Yuter and Houze (1997) and Yuter et al. (2005) for more detail. dB_averaging : bool, optional True if using dBZ reflectivity values that need to be converted to linear Z before averaging. False for other non-dBZ values (i.e. snow rate) @@ -177,6 +179,10 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, distributions over the Pacific warm pool and implications for Z-R relations. J. Appl. Meteor., 36, 847-867. + Yuter, S. E., R. A. Houze, Jr., E. A. Smith, T. T. Wilheit, and E. Zipser, + 2005: Physical characterization of tropical oceanic convection observed in + KWAJEX. J. Appl. Meteor., 44, 385-415. + """ # Maxmimum convective radius must be less than 5 km @@ -244,7 +250,7 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, + dB_averaging=dB_averaging, val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) # save into dictionaries From e8203cf5abf0c21e82310cce65f6238ae3c9375a Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 10:20:23 -0400 Subject: [PATCH 31/66] Add despeckling abilities --- pyart/retrieve/_echo_class_updated.py | 32 ++++++++++++++++++++++----- pyart/retrieve/echo_class.py | 26 +++++++++++++++------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 67a5469616..0761efa31b 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -5,8 +5,9 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0, - dB_averaging=False, val_for_max_conv_rad=30, max_conv_rad_km=5.0): + weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, + remove_small_objects=True, min_km2_size=10, + val_for_max_conv_rad=30, max_conv_rad_km=5.0): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point @@ -48,10 +49,14 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo dB_averaging : bool, optional True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + remove_small_objects : bool, optional + Determines if small objects should be removed from convective core array. Default is True. + min_km2_size : float, optional + Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. val_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional - Maximum radius around convective cores to classify as convective. Default is 5 km + Maximum radius around convective cores to classify as convective. Default is 5 km. Returns ------- @@ -98,8 +103,8 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, # Compute background radius refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres) - # mask background average - refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + # mask reflectivity field + refl = np.ma.masked_where(refl_bkg.mask, refl) # Get convective core array from cosine scheme, or scalar scheme if use_cosine: @@ -112,6 +117,23 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, conv_radius_km = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, max_conv_rad=max_conv_rad_km) + # remove small objects in convective core array + if remove_small_objects: + # calculate minimum pixel size given dx and dy + min_pix_size = min_km2_size / ((dx/1000) * (dy/1000)) + # label connected objects in convective core array + cc_labels, _ = scipy.ndimage.label(conv_core_array) + # mask labels where convective core array is masked + cc_labels = np.ma.masked_where(conv_core_array.mask, cc_labels) + + # loop through each unique label + for lab in np.unique(cc_labels): + # calculate number of pixels for each label + size_lab = np.count_nonzero(cc_labels == lab) + # if number of pixels is less than minimum, then remove core + if size_lab < min_pix_size: + conv_core_array[cc_labels == lab] = 0 + # Incorporate convective radius using binary dilation # Create empty array for assignment temp_assignment = np.zeros_like(conv_core_array) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index d8ecdc226b..1acf468899 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -110,12 +110,13 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0, - dB_averaging=False, dBZ_for_max_conv_rad=30, max_conv_rad_km=5.0, + weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=False, + remove_small_objects=True, min_km2_size=10, + val_for_max_conv_rad=30, max_conv_rad_km=5.0, fill_value=None, refl_field=None, estimate_flag=True, estimate_offset=5): """ - Partition reflectivity into convective-stratiform using the Yuter - and Houze (1997) algorithm. + Partition reflectivity into convective-stratiform using the Yuter et al. (2005) + and Yuter and Houze (1997) algorithm. Parameters ---------- @@ -151,7 +152,11 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, dB_averaging : bool, optional True if using dBZ reflectivity values that need to be converted to linear Z before averaging. False for other non-dBZ values (i.e. snow rate) - dBZ_for_max_conv_rad : float, optional + remove_small_objects : bool, optional + Determines if small objects should be removed from convective core array. Default is True. + min_km2_size : float, optional + Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. + val_for_max_conv_rad : float, optional dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km @@ -220,7 +225,8 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, + val_for_max_conv_rad=val_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) # put data into a dictionary to be added as a field convsf_dict = {'convsf': { @@ -243,7 +249,9 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, + val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad_km=max_conv_rad_km) _, _, convsf_over = _revised_conv_strat(ze + estimate_offset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, @@ -251,7 +259,9 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, - val_for_max_conv_rad=dBZ_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, + val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad_km=max_conv_rad_km) # save into dictionaries convsf_dict['convsf_under'] = { From 83ee0272c850bcd37da0c6e165119a15c44bd0ef Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 14:31:45 -0400 Subject: [PATCH 32/66] Add check for if reflectivity field is masked --- pyart/retrieve/_echo_class_updated.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 0761efa31b..1d1afab41e 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -245,8 +245,12 @@ def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): if dB_averaging: refl = 10 ** (refl / 10) + # check if reflectivity is masked array + if np.ma.isMaskedArray(refl): + refl = refl.filled(np.nan) + # calculate background reflectivity with circular footprint - refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', + refl_bkg = scipy.ndimage.generic_filter(refl, function=np.nanmean, mode='constant', footprint=bkg_mask_array.astype(bool), cval=np.nan) # if calc_thres is not none, then calculate the number of points used to calculate average From 816ded022dfa8228707d1314a1af1bad348f67c5 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 14:39:58 -0400 Subject: [PATCH 33/66] Add functionality for desired level --- pyart/retrieve/echo_class.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 1acf468899..715e7304b7 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -107,7 +107,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} -def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, +def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=8, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=False, @@ -125,6 +125,8 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, dx, dy : float, optional The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values parsed from grid object. + level_m : float, optional + Desired height to classify with convective stratiform algorithm. always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective See Yuter et al. (2005) for more detail. @@ -214,8 +216,16 @@ def conv_strat(grid, dx=None, dy=None, always_core_thres=42, bkg_rad_km=11, y = grid.y['data'] z = grid.z['data'] - # Get reflectivity data - ze = np.ma.copy(grid.fields[refl_field]['data']) + # Get reflectivity data at desired level + if level_m is None: + try: + ze = np.ma.copy(grid.fields[refl_field]['data'][0, :, :]) + except: + ze = np.ma.copy(grid.fields[refl_field]['data'][:, :]) + else: + zslice = np.argmin(np.abs(z - level_m)) + ze = np.ma.copy(grid.fields[refl_field]['data'][zslice, :, :]) + ze = ze.filled(np.NaN) # run convective stratiform algorithm From d3e365386f482496adff23797cf1183e0221dc7e Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 14:57:05 -0400 Subject: [PATCH 34/66] Change under to over --- pyart/retrieve/echo_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 715e7304b7..2c5ec668c6 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -287,7 +287,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r '2 = Convective')} convsf_dict['convsf_over'] = { - 'data': convsf_under, + 'data': convsf_over, 'standard_name': 'convsf_under', 'long_name': 'Convective stratiform classification (Overestimate)', 'valid_min': 0, From d2749da3b2ad7849ce2a7167eb4e1ae404e90948 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 15:11:47 -0400 Subject: [PATCH 35/66] Update default max_diff --- pyart/retrieve/echo_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 2c5ec668c6..18de3d11fe 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -108,7 +108,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, - use_cosine=True, max_diff=8, zero_diff_cos_val=55, + use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=False, remove_small_objects=True, min_km2_size=10, @@ -226,7 +226,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r zslice = np.argmin(np.abs(z - level_m)) ze = np.ma.copy(grid.fields[refl_field]['data'][zslice, :, :]) - ze = ze.filled(np.NaN) + #ze = ze.filled(np.NaN) # run convective stratiform algorithm _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, From 1196ca57c89bc275ffed3edadf25418444fea2d3 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 18 Oct 2022 15:12:08 -0400 Subject: [PATCH 36/66] Fix masking bugs --- pyart/retrieve/_echo_class_updated.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 1d1afab41e..478430bf16 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -3,7 +3,7 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, - use_cosine=True, max_diff=8, zero_diff_cos_val=55, + use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, remove_small_objects=True, min_km2_size=10, @@ -100,7 +100,8 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) # %% Convective stratiform detection - + # start by making reflectivity a masked array + refl = np.ma.masked_invalid(refl) # Compute background radius refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres) # mask reflectivity field @@ -152,7 +153,7 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, temp_assignment = temp_assignment + temp_dilated # add dilated cores to original array - conv_core_copy = np.copy(conv_core_array) + conv_core_copy = np.ma.copy(conv_core_array) conv_core_copy[temp_assignment >= 1] = CS_CORE # Now do convective stratiform classification @@ -160,6 +161,8 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, min_dBZ_used, weak_echo_thres) + # mask where reflectivity is masked + conv_strat_array = np.ma.masked_where(refl.mask, conv_strat_array) return refl_bkg, conv_core_array, conv_strat_array @@ -245,12 +248,8 @@ def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): if dB_averaging: refl = 10 ** (refl / 10) - # check if reflectivity is masked array - if np.ma.isMaskedArray(refl): - refl = refl.filled(np.nan) - # calculate background reflectivity with circular footprint - refl_bkg = scipy.ndimage.generic_filter(refl, function=np.nanmean, mode='constant', + refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', footprint=bkg_mask_array.astype(bool), cval=np.nan) # if calc_thres is not none, then calculate the number of points used to calculate average From c8cb69a63fb562be5a21c72cdbfab8b1f7adbcaa Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 25 Oct 2022 09:48:31 -0400 Subject: [PATCH 37/66] Set db_averaging default to True --- pyart/retrieve/_echo_class_updated.py | 2 +- pyart/retrieve/echo_class.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 478430bf16..66570d2ac7 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -5,7 +5,7 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=False, + weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0): """ diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 18de3d11fe..79d88c1a66 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -110,7 +110,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=False, + weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, fill_value=None, refl_field=None, estimate_flag=True, estimate_offset=5): From 6e88f99ef4cf2ef4ad01ded1ea7c5242f621da47 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 25 Oct 2022 09:54:23 -0400 Subject: [PATCH 38/66] Add in background radius recommendations and catch --- pyart/retrieve/_echo_class_updated.py | 2 +- pyart/retrieve/echo_class.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py index 66570d2ac7..6b82995495 100644 --- a/pyart/retrieve/_echo_class_updated.py +++ b/pyart/retrieve/_echo_class_updated.py @@ -28,7 +28,7 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkg_rad_km : float, optional - Radius to compute background reflectivity in kilometers. Default is 11 km + Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing use_cosine : bool, optional Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) max_diff : float, optional diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 79d88c1a66..9b34a4ca6d 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -131,7 +131,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r Threshold for points that are always convective. All values above the threshold are classifed as convective See Yuter et al. (2005) for more detail. bkg_rad_km : float, optional - Radius to compute background reflectivity in kilometers. Default is 11 km + Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing use_cosine : bool, optional Boolean used to determine if a cosine scheme (see Yuter and Houze (1997)) should be used for identifying convective cores (True) or if a simpler scalar scheme (False) should be used. max_diff : float, optional @@ -204,6 +204,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r # Parse field parameters if refl_field is None: refl_field = get_field_name('reflectivity') + dB_averaging = True # parse dx and dy if None if dx is None: @@ -211,6 +212,11 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r if dy is None: dy = grid.y['data'][1] - grid.y['data'][0] + # add catch for background radius size + if bkg_rad_km < 2 * 1000 * dx or bkg_rad_km < 2 * 1000 * dy: + print("Background radius for averaging must be at least 2 times dx and dy, exiting") + raise + # Get coordinates x = grid.x['data'] y = grid.y['data'] @@ -226,8 +232,6 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r zslice = np.argmin(np.abs(z - level_m)) ze = np.ma.copy(grid.fields[refl_field]['data'][zslice, :, :]) - #ze = ze.filled(np.NaN) - # run convective stratiform algorithm _, _, convsf_best = _revised_conv_strat(ze, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, From 980f764167bc08cf0465df5003b03a9ebbccff5e Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 12:05:52 -0400 Subject: [PATCH 39/66] Fix m/km correction --- pyart/retrieve/echo_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 9b34a4ca6d..71315c532f 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -213,7 +213,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r dy = grid.y['data'][1] - grid.y['data'][0] # add catch for background radius size - if bkg_rad_km < 2 * 1000 * dx or bkg_rad_km < 2 * 1000 * dy: + if bkg_rad_km * 1000 < 2 * dx or bkg_rad_km * 1000 < 2 * dy: print("Background radius for averaging must be at least 2 times dx and dy, exiting") raise From 5f4fadbce728e24155df7dfa159f469e76795093 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 12:54:17 -0400 Subject: [PATCH 40/66] Update reference --- pyart/retrieve/echo_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 71315c532f..08ba614a69 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -251,7 +251,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r 'valid_max': 3, 'comment_1': ('Convective-stratiform echo ' 'classification based on ' - 'Yuter and Houze (1997)'), + 'Yuter and Houze (1997) and Yuter et al. (2005)'), 'comment_2': ('0 = Undefined, 1 = Stratiform, ' '2 = Convective')}} @@ -286,7 +286,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r 'valid_max': 3, 'comment_1': ('Convective-stratiform echo ' 'classification based on ' - 'Yuter and Houze (1997)'), + 'Yuter and Houze (1997) and Yuter et al. (2005)'), 'comment_2': ('0 = Undefined, 1 = Stratiform, ' '2 = Convective')} From 736cbdf688a579eed8c46e4e18a54ed3329c56d2 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 12:55:33 -0400 Subject: [PATCH 41/66] Update comment --- pyart/retrieve/echo_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 08ba614a69..44957de28c 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -252,8 +252,8 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r 'comment_1': ('Convective-stratiform echo ' 'classification based on ' 'Yuter and Houze (1997) and Yuter et al. (2005)'), - 'comment_2': ('0 = Undefined, 1 = Stratiform, ' - '2 = Convective')}} + 'comment_2': ('0 = No surface echo/Undefined, 1 = Stratiform, ' + '2 = Convective, 3 = weak echo')}} # If estimation is True, run the algorithm on the field with offset subtracted and the field with the offset added if estimate_flag: @@ -288,7 +288,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r 'classification based on ' 'Yuter and Houze (1997) and Yuter et al. (2005)'), 'comment_2': ('0 = Undefined, 1 = Stratiform, ' - '2 = Convective')} + '2 = Convective, 3 = weak echo')} convsf_dict['convsf_over'] = { 'data': convsf_over, @@ -300,7 +300,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r 'classification based on ' 'Yuter and Houze (1997)'), 'comment_2': ('0 = Undefined, 1 = Stratiform, ' - '2 = Convective')} + '2 = Convective, 3 = weak echo')} return convsf_dict From 5a9a22629650eeea0057318237aac3d916eb65ae Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 13:41:49 -0400 Subject: [PATCH 42/66] Add test for new function --- tests/retrieve/test_echo_class.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/retrieve/test_echo_class.py b/tests/retrieve/test_echo_class.py index 9fe16d74c0..06030a4042 100644 --- a/tests/retrieve/test_echo_class.py +++ b/tests/retrieve/test_echo_class.py @@ -22,6 +22,24 @@ def test_steiner_conv_strat_modify_area(area_relation): assert eclass['data'].min() == 0 assert eclass['data'].max() == 2 +def test_conv_strat_default(): + grid = pyart.testing.make_storm_grid() + dict = pyart.retrieve.conv_strat(grid, bkg_rad_km=50) + + assert 'convsf' in dict.keys() + assert 'convsf_under' in dict.keys() + assert 'convsf_over' in dict.keys() + assert np.all(dict['convsf']['data'][25] == np.array( + [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0])) + +def test_conv_strat_noest(): + grid = pyart.testing.make_storm_grid() + dict = pyart.retrieve.conv_strat(grid, bkg_rad_km=50, estimate_flag=False) + + assert 'convsf' in dict.keys() + assert 'convsf_under' not in dict.keys() + assert 'convsf_over' not in dict.keys() def test_hydroclass_semisupervised(): radar = pyart.io.read(pyart.testing.NEXRAD_ARCHIVE_MSG31_FILE) From c921a05eaa9f0be1491874ef8041562c14fc1f6b Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 13:45:47 -0400 Subject: [PATCH 43/66] Move functions to correct file --- pyart/retrieve/_echo_class.py | 464 ++++++++++++++++++++++++- pyart/retrieve/_echo_class_updated.py | 465 -------------------------- pyart/retrieve/echo_class.py | 3 +- 3 files changed, 464 insertions(+), 468 deletions(-) delete mode 100644 pyart/retrieve/_echo_class_updated.py diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index 826d340e89..f1c930ccfa 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -1,5 +1,5 @@ import numpy as np - +import scipy.ndimage def _steiner_conv_strat(refl, x, y, dx, dy, intense=42, peak_relation=0, area_relation=1, bkg_rad=11000, use_intense=True): @@ -214,3 +214,465 @@ def steiner_class_buff(ze, x, y, z, dx, dy, bkg_rad, bkg_rad=11000, use_intense=True) return sclass + +def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, + use_cosine=True, max_diff=5, zero_diff_cos_val=55, + scalar_diff=1.5, use_addition=True, calc_thres=0.75, + weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, + remove_small_objects=True, min_km2_size=10, + val_for_max_conv_rad=30, max_conv_rad_km=5.0): + """ + We perform the Yuter and Houze (1997) algorithm for echo classification + using only the reflectivity field in order to classify each grid point + as either convective, stratiform or undefined. Grid points are + classified as follows, + + 0 = No Surface Echo/ Undefined + 1 = Stratiform + 2 = Convective + 3 = Weak Echo + + refl : array + array of reflectivity values + x, y : array + x and y coordinates of reflectivity array, respectively + dx, dy : float + The x- and y-dimension resolutions in meters, respectively. + always_core_thres : float, optional + Threshold for points that are always convective. All values above the threshold are classifed as convective + bkg_rad_km : float, optional + Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing + use_cosine : bool, optional + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) + max_diff : float, optional + Maximum difference between background average and reflectivity in order to be classified as convective. + "a" value in Eqn. B1 in Yuter and Houze (1997) + zero_diff_cos_val : float, optional + Value where difference between background average and reflectivity is zero in the cosine function + "b" value in Eqn. B1 in Yuter and Houze (1997) + scalar_diff : float, optional + If using a scalar difference scheme, this value is the multiplier or addition to the background average + use_addition : bool, optional + Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + calc_thres : float, optional + Minimum percentage of points needed to be considered in background average calculation + weak_echo_thres : float, optional + Threshold for determining weak echo. All values below this threshold will be considered weak echo + min_dBZ_used : float, optional + Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo + dB_averaging : bool, optional + True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values + remove_small_objects : bool, optional + Determines if small objects should be removed from convective core array. Default is True. + min_km2_size : float, optional + Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. + val_for_max_conv_rad : float, optional + dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius + max_conv_rad_km : float, optional + Maximum radius around convective cores to classify as convective. Default is 5 km. + + Returns + ------- + refl_bkg : array + Array of background values + conv_core_array : array + Array of initial convective cores (identified convective elements without convective radii applied) + conv_strat_array : array + Array of convective stratiform classifcation with convective radii applied + """ + + # Constants to fill arrays with + CS_CORE = 3 + NOSFCECHO = 0 + WEAKECHO = 3 + SF = 1 + CONV = 2 + + # Set up mask arrays for background average and + # prepare for convective mask arrays + # calculate maximum convective diameter from max. convective radius (input) + max_conv_diameter = int(np.floor((max_conv_rad_km / (dx / 1000)) * 2)) + # if diameter is even, make odd + if max_conv_diameter % 2 == 0: + max_conv_diameter = max_conv_diameter + 1 + # find center point + center_conv_mask_x = int(np.floor(max_conv_diameter / 2)) + + # prepare background mask array for computing background average + # calculate number of pixels for background array given requested background radius and dx + bkg_diameter_pix = int(np.floor((bkg_rad_km / (dx / 1000)) * 2)) + # set diameter to odd if even + if bkg_diameter_pix % 2 == 0: + bkg_diameter_pix = bkg_diameter_pix + 1 + # find center point + bkg_center = int(np.floor(bkg_diameter_pix / 2)) + # create background array + bkg_mask_array = np.ones((bkg_diameter_pix, bkg_diameter_pix), dtype=float) + # mask outside circular region + bkg_mask_array = create_radial_mask(bkg_mask_array, min_rad_km=0, max_rad_km=bkg_rad_km, x_pixsize=dx / 1000, + y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) + + # Convective stratiform detection + # start by making reflectivity a masked array + refl = np.ma.masked_invalid(refl) + # Compute background radius + refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres) + # mask reflectivity field + refl = np.ma.masked_where(refl_bkg.mask, refl) + + # Get convective core array from cosine scheme, or scalar scheme + if use_cosine: + conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE) + else: + conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, CS_CORE, + use_addition=use_addition) + + # Assign convective radii based on background reflectivity + conv_radius_km = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad=max_conv_rad_km) + + # remove small objects in convective core array + if remove_small_objects: + # calculate minimum pixel size given dx and dy + min_pix_size = min_km2_size / ((dx/1000) * (dy/1000)) + # label connected objects in convective core array + cc_labels, _ = scipy.ndimage.label(conv_core_array) + # mask labels where convective core array is masked + cc_labels = np.ma.masked_where(conv_core_array.mask, cc_labels) + + # loop through each unique label + for lab in np.unique(cc_labels): + # calculate number of pixels for each label + size_lab = np.count_nonzero(cc_labels == lab) + # if number of pixels is less than minimum, then remove core + if size_lab < min_pix_size: + conv_core_array[cc_labels == lab] = 0 + + # Incorporate convective radius using binary dilation + # Create empty array for assignment + temp_assignment = np.zeros_like(conv_core_array) + + # Loop through radii + for radius in np.arange(1, max_conv_rad_km + 1): + # create mask array for radius incorporation + conv_mask_array = create_conv_radius_mask(max_conv_diameter, radius, dx / 1000, dy / 1000, center_conv_mask_x) + # find location of radius + temp = conv_radius_km == radius + # get cores for given radius + temp_core = np.ma.masked_where(~temp, conv_core_array) + # dilate cores + temp_dilated = scipy.ndimage.binary_dilation(temp_core.filled(0), conv_mask_array) + # add to assignment array + temp_assignment = temp_assignment + temp_dilated + + # add dilated cores to original array + conv_core_copy = np.ma.copy(conv_core_array) + conv_core_copy[temp_assignment >= 1] = CS_CORE + + # Now do convective stratiform classification + conv_strat_array = np.zeros_like(refl) + conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + min_dBZ_used, weak_echo_thres) + # mask where reflectivity is masked + conv_strat_array = np.ma.masked_where(refl.mask, conv_strat_array) + + return refl_bkg, conv_core_array, conv_strat_array + + +# functions + +def create_radial_mask(mask_array, min_rad_km, max_rad_km, x_pixsize, + y_pixsize, center_x, center_y, circular=True): + """ + Computes a radial distance mask, everything with distance between minradiuskm + and maxradiuskm is assigned 1, everything else is assigned 0. This version can + handle rectangular arrays and pixels as well as square ones. + + Parameters + ---------- + mask_array : array + Array to mask + min_rad_km, max_rad_km : float + The minimum and maximum radius of the non-masked region in kilometers. + x_pixsize, y_pixsize : float + The pixel size in the x- and y-dimension in kilometers, respectively + center_x, center_y : int + The center pixel in the x- and y-dimension, respectively + circular : bool + True returns circular mask, False returns a rectangular mask. + + Returns + ------- + mask_array : array + Rectangular array masked by a radial distance. + """ + + xsize, ysize = mask_array.shape + + for j in np.arange(0, ysize, 1): + for i in np.arange(0, xsize, 1): + # compute range to pixel + if circular: + x_range_sq = ((center_x - i) * x_pixsize) ** 2 + y_range_sq = ((center_y - j) * y_pixsize) ** 2 + circ_range = np.sqrt(x_range_sq + y_range_sq) + # if circular is False, use square mask + else: + x_range = abs(int(np.floor(center_x - i) * x_pixsize)) + y_range = abs(int(np.floor(center_y - j) * y_pixsize)) + + if x_range > y_range: + circ_range = x_range + else: + circ_range = y_range + # if range is within min and max, set to True + if (circ_range <= max_rad_km) and (circ_range >= min_rad_km): + mask_array[j, i] = 1 + else: + mask_array[j, i] = 0 + + return mask_array + + +def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): + """ + Calculate the background of the given refl array. The footprint used to + calculate the average for each pixel is given by bkg_mask_array + + Parameters + ---------- + refl : array + Reflectivity array to compute average + bkg_mask_array : array + Array of radial points to use for average + dB_averaging : bool + If True, converts dBZ to linear Z before averaging + calc_thres : float + Minimum percentage of points needed to be considered in background average calculation + + Returns + ------- + refl_bkg : array + Array of average values + """ + + # if dBaverage is true, convert reflectivity to linear Z + if dB_averaging: + refl = 10 ** (refl / 10) + + # calculate background reflectivity with circular footprint + refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', + footprint=bkg_mask_array.astype(bool), cval=np.nan) + + # if calc_thres is not none, then calculate the number of points used to calculate average + if calc_thres is not None: + # count valid points + refl_count = scipy.ndimage.generic_filter(refl.filled(0), function=np.count_nonzero, mode='constant', + footprint=bkg_mask_array.astype(bool), cval=0) + # find threshold number of points + val = calc_thres * np.count_nonzero(bkg_mask_array) + # mask out values + refl_bkg = np.ma.masked_where(refl_count < val, refl_bkg) + + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + # if dBaveraging is true, convert background reflectivity to dBZ + if dB_averaging: + refl_bkg = 10 * np.log10(refl_bkg) + # mask where original reflectivity is invalid + refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) + + return refl_bkg + + +def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE): + """ + Function for assigning convective cores based on a cosine function + + Parameters + ---------- + refl : array + Reflectivity values + refl_bkg : array + Background average of reflectivity values + max_diff : float + Maximum difference between refl and refl_bkg needed for convective classification + zero_diff_cos_val : float + Value where the cosine function returns a zero difference + always_core_thres : float + All values above this threshold considered to be convective + CS_CORE : int + Value assigned to convective pixels + + Returns + ------- + conv_core_array : array + Array of booleans if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zeDiff for entire array + zDiff = max_diff * np.cos((np.pi * refl_bkg) / (2 * zero_diff_cos_val)) + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[refl_bkg < 0] = max_diff # where background less than zero, set to max. diff + + # set values + conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core + conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + + +def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_core_thres, CS_CORE, use_addition=False): + """ + Function for assigning convective cores based on a scalar difference + + Parameters + ---------- + refl : array + Reflectivity values + refl_bkg : array + Background average of reflectivity values + max_diff : float + Maximum difference between refl and refl_bkg needed for convective classification + always_core_thres : float + All values above this threshold considered to be convective + CS_CORE : int + Value assigned to convective pixels + use_addition : bool + Boolean to determine if scalar should be added (True) or multiplied (False) + + Returns + ------- + conv_core_array : array + Array of booleans if point is convective (1) or not (0) + """ + + # initialize entire array to not a convective core + conv_core_array = np.zeros_like(refl) + + # calculate zDiff for entire array + # if addition, add difference. Else, multiply difference + if use_addition: + zDiff = max_diff + refl_bkg + else: + zDiff = max_diff * refl_bkg + + zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero + zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero + + # set values + conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core + conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core + + return conv_core_array + + +def create_conv_radius_mask(max_conv_diameter, radius_km, x_spacing, y_spacing, center_conv_mask_x): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + max_conv_diameter : int + maximum convective diameter in kilometers + radius_km : int + convective radius in kilometers + x_spacing, y_spacing : float + x- and y-dimension pixel size in meters, respectively + center_conv_mask_x : int + index of center point + + Returns + ------- + conv_mask_array : array + array masked based on distance of convective diameter + """ + + conv_mask_array = np.zeros((max_conv_diameter, max_conv_diameter)) + conv_mask_array = create_radial_mask(conv_mask_array, 0, radius_km, x_spacing, y_spacing, center_conv_mask_x, + center_conv_mask_x, True) + + return conv_mask_array + + +def assign_conv_radius_km(refl_bkg, val_for_max_conv_rad, max_conv_rad=5): + """ + Assigns the convective radius in kilometers based on the background values + + Parameters + ---------- + refl_bkg : array + array of background reflectivity values + val_for_max_conv_rad : float + reflectivity value for maximum convective radius (5 km) + max_conv_rad : float, optional + maximum convective radius in kilometers + + Returns + ------- + convRadiuskm : array + array of convective radii based on background values and val for max. conv radius + """ + + convRadiuskm = np.ones_like(refl_bkg) + + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 15)] = max_conv_rad - 3 + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 10)] = max_conv_rad - 2 + convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 5)] = max_conv_rad - 1 + convRadiuskm[refl_bkg >= val_for_max_conv_rad] = max_conv_rad + + return convRadiuskm + + +def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, + NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): + """ + Does and initial convective stratiform classification + + Parameters + ---------- + refl : array + Array of reflectivity values + conv_strat_array : array + Array with convective stratiform classifications + conv_core_array : array + Array with convective cores + NOSFCECHO : int + Value to assign points classified as no surface echo + CONV : int + Value to assign points classified as convective + SF : int + Value to assign points classified as stratiform + WEAKECHO : int + Value to assign points classfied as weak echo + CS_CORE : int + Value assigned to convective cores in conv_core_array + MINDBZUSE : float + Minimum dBZ value to consider in classification, all values below this will be set to NOSFCECHO + WEAKECHOTHRES : float + dBZ threshold for weak echo classification, all values below this will be set to WEAKECHO + + Returns + ------- + conv_strat_array : array + array of classifications + """ + + # assuming order so that each point is only assigned one time, no overlapping assignment + # initially, assign every point to stratiform + conv_strat_array[:] = SF + # where reflectivity is masked, set to no surface echo + conv_strat_array[refl.mask] = NOSFCECHO + # assign convective cores to CONV + conv_strat_array[conv_core_array == CS_CORE] = CONV + # assign reflectivity less than weakechothres to weak echo + conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO + # assign reflectivity less than minimum to no surface echo + conv_strat_array[refl < MINDBZUSE] = NOSFCECHO + + return conv_strat_array diff --git a/pyart/retrieve/_echo_class_updated.py b/pyart/retrieve/_echo_class_updated.py deleted file mode 100644 index 6b82995495..0000000000 --- a/pyart/retrieve/_echo_class_updated.py +++ /dev/null @@ -1,465 +0,0 @@ -import numpy as np -import scipy.ndimage - - -def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, - use_cosine=True, max_diff=5, zero_diff_cos_val=55, - scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, - remove_small_objects=True, min_km2_size=10, - val_for_max_conv_rad=30, max_conv_rad_km=5.0): - """ - We perform the Yuter and Houze (1997) algorithm for echo classification - using only the reflectivity field in order to classify each grid point - as either convective, stratiform or undefined. Grid points are - classified as follows, - - 0 = No Surface Echo/ Undefined - 1 = Stratiform - 2 = Convective - 3 = Weak Echo - - refl : array - array of reflectivity values - x, y : array - x and y coordinates of reflectivity array, respectively - dx, dy : float - The x- and y-dimension resolutions in meters, respectively. - always_core_thres : float, optional - Threshold for points that are always convective. All values above the threshold are classifed as convective - bkg_rad_km : float, optional - Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing - use_cosine : bool, optional - Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) - max_diff : float, optional - Maximum difference between background average and reflectivity in order to be classified as convective. - "a" value in Eqn. B1 in Yuter and Houze (1997) - zero_diff_cos_val : float, optional - Value where difference between background average and reflectivity is zero in the cosine function - "b" value in Eqn. B1 in Yuter and Houze (1997) - scalar_diff : float, optional - If using a scalar difference scheme, this value is the multiplier or addition to the background average - use_addition : bool, optional - Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used - calc_thres : float, optional - Minimum percentage of points needed to be considered in background average calculation - weak_echo_thres : float, optional - Threshold for determining weak echo. All values below this threshold will be considered weak echo - min_dBZ_used : float, optional - Minimum dBZ value used for classification. All values below this threshold will be considered no surface echo - dB_averaging : bool, optional - True if using dBZ values that need to be converted to linear Z before averaging. False for other types of values - remove_small_objects : bool, optional - Determines if small objects should be removed from convective core array. Default is True. - min_km2_size : float, optional - Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. - val_for_max_conv_rad : float, optional - dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius - max_conv_rad_km : float, optional - Maximum radius around convective cores to classify as convective. Default is 5 km. - - Returns - ------- - refl_bkg : array - Array of background values - conv_core_array : array - Array of initial convective cores (identified convective elements without convective radii applied) - conv_strat_array : array - Array of convective stratiform classifcation with convective radii applied - """ - - # Constants to fill arrays with - CS_CORE = 3 - NOSFCECHO = 0 - WEAKECHO = 3 - SF = 1 - CONV = 2 - - # %% Set up mask arrays for background average and - # prepare for convective mask arrays - # calculate maximum convective diameter from max. convective radius (input) - max_conv_diameter = int(np.floor((max_conv_rad_km / (dx / 1000)) * 2)) - # if diameter is even, make odd - if max_conv_diameter % 2 == 0: - max_conv_diameter = max_conv_diameter + 1 - # find center point - center_conv_mask_x = int(np.floor(max_conv_diameter / 2)) - - # prepare background mask array for computing background average - # calculate number of pixels for background array given requested background radius and dx - bkg_diameter_pix = int(np.floor((bkg_rad_km / (dx / 1000)) * 2)) - # set diameter to odd if even - if bkg_diameter_pix % 2 == 0: - bkg_diameter_pix = bkg_diameter_pix + 1 - # find center point - bkg_center = int(np.floor(bkg_diameter_pix / 2)) - # create background array - bkg_mask_array = np.ones((bkg_diameter_pix, bkg_diameter_pix), dtype=float) - # mask outside circular region - bkg_mask_array = create_radial_mask(bkg_mask_array, min_rad_km=0, max_rad_km=bkg_rad_km, x_pixsize=dx / 1000, - y_pixsize=dy / 1000, center_x=bkg_center, center_y=bkg_center, circular=True) - - # %% Convective stratiform detection - # start by making reflectivity a masked array - refl = np.ma.masked_invalid(refl) - # Compute background radius - refl_bkg = calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres) - # mask reflectivity field - refl = np.ma.masked_where(refl_bkg.mask, refl) - - # Get convective core array from cosine scheme, or scalar scheme - if use_cosine: - conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE) - else: - conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, CS_CORE, - use_addition=use_addition) - - # Assign convective radii based on background reflectivity - conv_radius_km = assign_conv_radius_km(refl_bkg, val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad=max_conv_rad_km) - - # remove small objects in convective core array - if remove_small_objects: - # calculate minimum pixel size given dx and dy - min_pix_size = min_km2_size / ((dx/1000) * (dy/1000)) - # label connected objects in convective core array - cc_labels, _ = scipy.ndimage.label(conv_core_array) - # mask labels where convective core array is masked - cc_labels = np.ma.masked_where(conv_core_array.mask, cc_labels) - - # loop through each unique label - for lab in np.unique(cc_labels): - # calculate number of pixels for each label - size_lab = np.count_nonzero(cc_labels == lab) - # if number of pixels is less than minimum, then remove core - if size_lab < min_pix_size: - conv_core_array[cc_labels == lab] = 0 - - # Incorporate convective radius using binary dilation - # Create empty array for assignment - temp_assignment = np.zeros_like(conv_core_array) - - # Loop through radii - for radius in np.arange(1, max_conv_rad_km + 1): - # create mask array for radius incorporation - conv_mask_array = create_conv_radius_mask(max_conv_diameter, radius, dx / 1000, dy / 1000, center_conv_mask_x) - # find location of radius - temp = conv_radius_km == radius - # get cores for given radius - temp_core = np.ma.masked_where(~temp, conv_core_array) - # dilate cores - temp_dilated = scipy.ndimage.binary_dilation(temp_core.filled(0), conv_mask_array) - # add to assignment array - temp_assignment = temp_assignment + temp_dilated - - # add dilated cores to original array - conv_core_copy = np.ma.copy(conv_core_array) - conv_core_copy[temp_assignment >= 1] = CS_CORE - - # Now do convective stratiform classification - conv_strat_array = np.zeros_like(refl) - conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, - min_dBZ_used, weak_echo_thres) - # mask where reflectivity is masked - conv_strat_array = np.ma.masked_where(refl.mask, conv_strat_array) - - return refl_bkg, conv_core_array, conv_strat_array - - -# functions - -def create_radial_mask(mask_array, min_rad_km, max_rad_km, x_pixsize, - y_pixsize, center_x, center_y, circular=True): - """ - Computes a radial distance mask, everything with distance between minradiuskm - and maxradiuskm is assigned 1, everything else is assigned 0. This version can - handle rectangular arrays and pixels as well as square ones. - - Parameters - ---------- - mask_array : array - Array to mask - min_rad_km, max_rad_km : float - The minimum and maximum radius of the non-masked region in kilometers. - x_pixsize, y_pixsize : float - The pixel size in the x- and y-dimension in kilometers, respectively - center_x, center_y : int - The center pixel in the x- and y-dimension, respectively - circular : bool - True returns circular mask, False returns a rectangular mask. - - Returns - ------- - mask_array : array - Rectangular array masked by a radial distance. - """ - - xsize, ysize = mask_array.shape - - for j in np.arange(0, ysize, 1): - for i in np.arange(0, xsize, 1): - # compute range to pixel - if circular: - x_range_sq = ((center_x - i) * x_pixsize) ** 2 - y_range_sq = ((center_y - j) * y_pixsize) ** 2 - circ_range = np.sqrt(x_range_sq + y_range_sq) - # if circular is False, use square mask - else: - x_range = abs(int(np.floor(center_x - i) * x_pixsize)) - y_range = abs(int(np.floor(center_y - j) * y_pixsize)) - - if x_range > y_range: - circ_range = x_range - else: - circ_range = y_range - # if range is within min and max, set to True - if (circ_range <= max_rad_km) and (circ_range >= min_rad_km): - mask_array[j, i] = 1 - else: - mask_array[j, i] = 0 - - return mask_array - - -def calc_bkg_intensity(refl, bkg_mask_array, dB_averaging, calc_thres=None): - """ - Calculate the background of the given refl array. The footprint used to - calculate the average for each pixel is given by bkg_mask_array - - Parameters - ---------- - refl : array - Reflectivity array to compute average - bkg_mask_array : array - Array of radial points to use for average - dB_averaging : bool - If True, converts dBZ to linear Z before averaging - calc_thres : float - Minimum percentage of points needed to be considered in background average calculation - - Returns - ------- - refl_bkg : array - Array of average values - """ - - # if dBaverage is true, convert reflectivity to linear Z - if dB_averaging: - refl = 10 ** (refl / 10) - - # calculate background reflectivity with circular footprint - refl_bkg = scipy.ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', - footprint=bkg_mask_array.astype(bool), cval=np.nan) - - # if calc_thres is not none, then calculate the number of points used to calculate average - if calc_thres is not None: - # count valid points - refl_count = scipy.ndimage.generic_filter(refl.filled(0), function=np.count_nonzero, mode='constant', - footprint=bkg_mask_array.astype(bool), cval=0) - # find threshold number of points - val = calc_thres * np.count_nonzero(bkg_mask_array) - # mask out values - refl_bkg = np.ma.masked_where(refl_count < val, refl_bkg) - - # mask where original reflectivity is invalid - refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) - - # if dBaveraging is true, convert background reflectivity to dBZ - if dB_averaging: - refl_bkg = 10 * np.log10(refl_bkg) - # mask where original reflectivity is invalid - refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) - - return refl_bkg - - -def convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE): - """ - Function for assigning convective cores based on a cosine function - - Parameters - ---------- - refl : array - Reflectivity values - refl_bkg : array - Background average of reflectivity values - max_diff : float - Maximum difference between refl and refl_bkg needed for convective classification - zero_diff_cos_val : float - Value where the cosine function returns a zero difference - always_core_thres : float - All values above this threshold considered to be convective - CS_CORE : int - Value assigned to convective pixels - - Returns - ------- - conv_core_array : array - Array of booleans if point is convective (1) or not (0) - """ - - # initialize entire array to not a convective core - conv_core_array = np.zeros_like(refl) - - # calculate zeDiff for entire array - zDiff = max_diff * np.cos((np.pi * refl_bkg) / (2 * zero_diff_cos_val)) - zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = max_diff # where background less than zero, set to max. diff - - # set values - conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core - conv_core_array[(refl - refl_bkg) >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core - - return conv_core_array - - -def convcore_scalar_scheme(refl, refl_bkg, max_diff, always_core_thres, CS_CORE, use_addition=False): - """ - Function for assigning convective cores based on a scalar difference - - Parameters - ---------- - refl : array - Reflectivity values - refl_bkg : array - Background average of reflectivity values - max_diff : float - Maximum difference between refl and refl_bkg needed for convective classification - always_core_thres : float - All values above this threshold considered to be convective - CS_CORE : int - Value assigned to convective pixels - use_addition : bool - Boolean to determine if scalar should be added (True) or multiplied (False) - - Returns - ------- - conv_core_array : array - Array of booleans if point is convective (1) or not (0) - """ - - # initialize entire array to not a convective core - conv_core_array = np.zeros_like(refl) - - # calculate zDiff for entire array - # if addition, add difference. Else, multiply difference - if use_addition: - zDiff = max_diff + refl_bkg - else: - zDiff = max_diff * refl_bkg - - zDiff[zDiff < 0] = 0 # where difference less than zero, set to zero - zDiff[refl_bkg < 0] = 0 # where background less than zero, set to zero - - # set values - conv_core_array[refl >= always_core_thres] = CS_CORE # where Z is greater than always_core_thres, set to core - conv_core_array[refl >= zDiff] = CS_CORE # where difference exceeeds minimum, set to core - - return conv_core_array - - -def create_conv_radius_mask(max_conv_diameter, radius_km, x_spacing, y_spacing, center_conv_mask_x): - """ - Does and initial convective stratiform classification - - Parameters - ---------- - max_conv_diameter : int - maximum convective diameter in kilometers - radius_km : int - convective radius in kilometers - x_spacing, y_spacing : float - x- and y-dimension pixel size in meters, respectively - center_conv_mask_x : int - index of center point - - Returns - ------- - conv_mask_array : array - array masked based on distance of convective diameter - """ - - conv_mask_array = np.zeros((max_conv_diameter, max_conv_diameter)) - conv_mask_array = create_radial_mask(conv_mask_array, 0, radius_km, x_spacing, y_spacing, center_conv_mask_x, - center_conv_mask_x, True) - - return conv_mask_array - - -def assign_conv_radius_km(refl_bkg, val_for_max_conv_rad, max_conv_rad=5): - """ - Assigns the convective radius in kilometers based on the background values - - Parameters - ---------- - refl_bkg : array - array of background reflectivity values - val_for_max_conv_rad : float - reflectivity value for maximum convective radius (5 km) - max_conv_rad : float, optional - maximum convective radius in kilometers - - Returns - ------- - convRadiuskm : array - array of convective radii based on background values and val for max. conv radius - """ - - convRadiuskm = np.ones_like(refl_bkg) - - convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 15)] = max_conv_rad - 3 - convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 10)] = max_conv_rad - 2 - convRadiuskm[refl_bkg >= (val_for_max_conv_rad - 5)] = max_conv_rad - 1 - convRadiuskm[refl_bkg >= val_for_max_conv_rad] = max_conv_rad - - return convRadiuskm - - -def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, MINDBZUSE, WEAKECHOTHRES): - """ - Does and initial convective stratiform classification - - Parameters - ---------- - refl : array - Array of reflectivity values - conv_strat_array : array - Array with convective stratiform classifications - conv_core_array : array - Array with convective cores - NOSFCECHO : int - Value to assign points classified as no surface echo - CONV : int - Value to assign points classified as convective - SF : int - Value to assign points classified as stratiform - WEAKECHO : int - Value to assign points classfied as weak echo - CS_CORE : int - Value assigned to convective cores in conv_core_array - MINDBZUSE : float - Minimum dBZ value to consider in classification, all values below this will be set to NOSFCECHO - WEAKECHOTHRES : float - dBZ threshold for weak echo classification, all values below this will be set to WEAKECHO - - Returns - ------- - conv_strat_array : array - array of classifications - """ - - # assuming order so that each point is only assigned one time, no overlapping assignment - # initially, assign every point to stratiform - conv_strat_array[:] = SF - # where reflectivity is masked, set to no surface echo - conv_strat_array[refl.mask] = NOSFCECHO - # assign convective cores to CONV - conv_strat_array[conv_core_array == CS_CORE] = CONV - # assign reflectivity less than weakechothres to weak echo - conv_strat_array[refl < WEAKECHOTHRES] = WEAKECHO - # assign reflectivity less than minimum to no surface echo - conv_strat_array[refl < MINDBZUSE] = NOSFCECHO - - return conv_strat_array diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 44957de28c..d30b60810a 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -7,8 +7,7 @@ from ..config import get_fillvalue, get_field_name, get_metadata from ..exceptions import MissingOptionalDependency -from ._echo_class import steiner_class_buff -from ._echo_class_updated import _revised_conv_strat +from ._echo_class import steiner_class_buff, _revised_conv_strat from warnings import warn From defe56e1df4d08d677cd60df125732c0136cda91 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 13:49:10 -0400 Subject: [PATCH 44/66] Remove unnecessary variables --- pyart/retrieve/echo_class.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index d30b60810a..f6f39a75e2 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -112,7 +112,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, - fill_value=None, refl_field=None, estimate_flag=True, estimate_offset=5): + refl_field=None, estimate_flag=True, estimate_offset=5): """ Partition reflectivity into convective-stratiform using the Yuter et al. (2005) and Yuter and Houze (1997) algorithm. @@ -161,9 +161,6 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km - fill_value : float, optional - Missing value used to signify bad data points. A value of None will use the default fill value as - defined in the Py-ART configuration file. refl_field : str, optional Field in grid to use as the reflectivity during partitioning. None will use the default reflectivity field name from the Py-ART configuration file. @@ -196,10 +193,6 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r print("Max conv radius must be less than 5 km, exiting") raise - # Get fill value for reflectivity field - if fill_value is None: - fill_value = get_fillvalue() - # Parse field parameters if refl_field is None: refl_field = get_field_name('reflectivity') @@ -217,8 +210,6 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r raise # Get coordinates - x = grid.x['data'] - y = grid.y['data'] z = grid.z['data'] # Get reflectivity data at desired level From 2e5a27ca860f8ddb142c72821dcd4b29ebefe036 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 13:52:13 -0400 Subject: [PATCH 45/66] Wrap text in comment --- pyart/retrieve/echo_class.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index f6f39a75e2..5322acd275 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -3,13 +3,12 @@ """ +from warnings import warn + import numpy as np -from ..config import get_fillvalue, get_field_name, get_metadata -from ..exceptions import MissingOptionalDependency from ._echo_class import steiner_class_buff, _revised_conv_strat - -from warnings import warn +from ..config import get_fillvalue, get_field_name, get_metadata def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, @@ -130,9 +129,11 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r Threshold for points that are always convective. All values above the threshold are classifed as convective See Yuter et al. (2005) for more detail. bkg_rad_km : float, optional - Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing + Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x + grid spacing use_cosine : bool, optional - Boolean used to determine if a cosine scheme (see Yuter and Houze (1997)) should be used for identifying convective cores (True) or if a simpler scalar scheme (False) should be used. + Boolean used to determine if a cosine scheme (see Yuter and Houze (1997)) should be used for identifying + convective cores (True) or if a simpler scalar scheme (False) should be used. max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. "a" value in Eqn. B1 in Yuter and Houze (1997) @@ -156,9 +157,11 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r remove_small_objects : bool, optional Determines if small objects should be removed from convective core array. Default is True. min_km2_size : float, optional - Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. + Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 + km^2. val_for_max_conv_rad : float, optional - dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius + dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective + radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km refl_field : str, optional @@ -169,7 +172,8 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r wih the estimate_offset added and the same field with the estimate_offset subtracted. Default is True (recommended) estimate_offset : float, optional - Value used to offset the reflectivity values by for the over/underestimation application. Default value is 5 dBZ. + Value used to offset the reflectivity values by for the over/underestimation application. Default value is 5 + dBZ. Returns ------- From 3dcc93f8e020a52e7e213e3bec1aaef5521926c4 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 26 Oct 2022 13:52:59 -0400 Subject: [PATCH 46/66] Wrap text --- pyart/retrieve/_echo_class.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index f1c930ccfa..ca00e2e2ec 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -241,9 +241,11 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective bkg_rad_km : float, optional - Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x grid spacing + Radius to compute background reflectivity in kilometers. Default is 11 km. Recommended to be at least 3 x + grid spacing use_cosine : bool, optional - Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar scheme (False) + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar + scheme (False) max_diff : float, optional Maximum difference between background average and reflectivity in order to be classified as convective. "a" value in Eqn. B1 in Yuter and Houze (1997) @@ -265,9 +267,11 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, remove_small_objects : bool, optional Determines if small objects should be removed from convective core array. Default is True. min_km2_size : float, optional - Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 km^2. + Minimum size of convective cores to be considered. Cores less than this size will be removed. Default is 10 + km^2. val_for_max_conv_rad : float, optional - dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective radius + dBZ for maximum convective radius. Convective cores with values above this will have the maximum convective + radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km. From fa8cbdba2e44f2184a18cfc389a87bb57aaa499d Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 2 Nov 2022 15:35:48 -0400 Subject: [PATCH 47/66] Create plot_convective_stratiform.py --- .../retrieve/plot_convective_stratiform.py | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 examples/retrieve/plot_convective_stratiform.py diff --git a/examples/retrieve/plot_convective_stratiform.py b/examples/retrieve/plot_convective_stratiform.py new file mode 100644 index 0000000000..2410058bb8 --- /dev/null +++ b/examples/retrieve/plot_convective_stratiform.py @@ -0,0 +1,250 @@ +""" +======================================= +Convective-Stratiform classification examples +======================================= +This example shows how to use the updated convective stratiform classifcation algorithm. We show 3 examples, +a summer convective example, an example from Hurricane Ian, and an example from a winter storm. + +""" + +print(__doc__) + +# Author: Laura Tomkins (lmtomkin@ncsu.edu) +# License: BSD 3 clause + + +import pyart +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +import cartopy.crs as ccrs + +###################################### +# **Example with summer convection** +# + + +# read in file +filename = pyart.testing.get_test_data('swx_20120520_0641.nc') +radar = pyart.io.read(filename) + +# extract the lowest sweep +radar = radar.extract_sweeps([0]) + +# interpolate to grid +grid = pyart.map.grid_from_radars( + (radar,), grid_shape=(1, 201, 201), + grid_limits=((0, 10000), (-50000.0, 50000.0), (-50000.0, 50000.0)), + fields=['reflectivity_horizontal']) + +# get dx dy +dx = grid.x['data'][1] - grid.x['data'][0] +dy = grid.y['data'][1] - grid.y['data'][0] + +# convective stratiform classification +convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='reflectivity_horizontal', always_core_thres=40, + bkg_rad_km=20, use_cosine=True, max_diff=5, zero_diff_cos_val=55, + weak_echo_thres=10, max_conv_rad_km=2) + +# add to grid object +# mask zero values (no surface echo) +convsf_masked = np.ma.masked_equal(convsf_dict['convsf']['data'], 0) +# mask 3 values (weak echo) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +# add dimension to array to add to grid object +convsf_dict['convsf']['data'] = convsf_masked[None,:,:] +# add field +grid.add_field('convsf', convsf_dict['convsf'], replace_existing=True) + +# create plot using GridMapDisplay +# plot variables +display = pyart.graph.GridMapDisplay(grid) +magma_r_cmap = plt.get_cmap('magma_r') +ref_cmap = mcolors.LinearSegmentedColormap.from_list('ref_cmap', magma_r_cmap(np.linspace(0, 0.9, magma_r_cmap.N))) +projection = ccrs.LambertConformal(central_latitude=radar.latitude['data'][0], + central_longitude=radar.longitude['data'][0]) +###################################### +# You'll notice that the convective stratiform field has less data around the edges compared to the reflectivity +# field. This is because the function is designed to only compute the background radius where the footprint has 75% +# of data, so along the edges there is not enough data to fulfill this requirement. The footprint percentage can be +# changed using the variable, calc_thres. + +# plot +plt.figure(figsize=(10,4)) +ax1=plt.subplot(1,2,1, projection=projection) +display.plot_grid('reflectivity_horizontal', vmin=5, vmax=45, cmap=ref_cmap, projection=projection, + transform=ccrs.PlateCarree(), ax=ax1) +ax2=plt.subplot(1,2,2, projection=projection) +display.plot_grid('convsf', vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3), projection=projection, ax=ax2, + transform=ccrs.PlateCarree(), ticks=[1/3, 1, 5/3], ticklabs=['', 'Stratiform', 'Convective']) +plt.show() + + +###################################### +# In addition to the default convective-stratiform classification, the function also returns an underestimate ( +# convsf_under) and an overestimate (convsf_over) to take into consideration the uncertainty when choosing +# classification parameters. The under and overestimate use the same parameters, but vary the input field by a +# certain value (default is 5 dBZ). The estimation can be turned off (estimate_flag=False), but we recommend keeping +# it turned on. + +# mask weak echo and no surface echo +convsf_masked = np.ma.masked_equal(convsf_dict['convsf']['data'], 0) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +convsf_dict['convsf']['data'] = convsf_masked +# underest. +convsf_masked = np.ma.masked_equal(convsf_dict['convsf_under']['data'], 0) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +convsf_dict['convsf_under']['data'] = convsf_masked +# overest. +convsf_masked = np.ma.masked_equal(convsf_dict['convsf_over']['data'], 0) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +convsf_dict['convsf_over']['data'] = convsf_masked + +# Plot each estimation +plt.figure(figsize=(10,4)) +ax1=plt.subplot(131) +ax1.pcolormesh(convsf_dict['convsf']['data'], vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3)) +ax1.set_title('Best estimate') +ax1.set_aspect('equal') +ax2=plt.subplot(132) +ax2.pcolormesh(convsf_dict['convsf_under']['data'], vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3)) +ax2.set_title('Underestimate') +ax2.set_aspect('equal') +ax3=plt.subplot(133) +ax3.pcolormesh(convsf_dict['convsf_over']['data'], vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3)) +ax3.set_title('Overestimate') +ax3.set_aspect('equal') +plt.show() + +###################################### +# **Tropical example** +# Let's get a NEXRAD file from Hurricane Ian + +# Read in file +nexrad_file = 's3://noaa-nexrad-level2/2022/09/28/KTBW/KTBW20220928_190142_V06' +radar = pyart.io.read_nexrad_archive(nexrad_file) + +# extract the lowest sweep +radar = radar.extract_sweeps([0]) + +# interpolate to grid +grid = pyart.map.grid_from_radars( + (radar,), grid_shape=(1, 201, 201), + grid_limits=((0, 10000), (-200000.0, 200000.0), (-200000.0, 200000.0)), + fields=['reflectivity']) + +# get dx dy +dx = grid.x['data'][1] - grid.x['data'][0] +dy = grid.y['data'][1] - grid.y['data'][0] + +# convective stratiform classification +convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='reflectivity', always_core_thres=40, + bkg_rad_km=20, use_cosine=True, max_diff=3, zero_diff_cos_val=55, + weak_echo_thres=5, max_conv_rad_km=2) + +# add to grid object +# mask zero values (no surface echo) +convsf_masked = np.ma.masked_equal(convsf_dict['convsf']['data'], 0) +# mask 3 values (weak echo) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +# add dimension to array to add to grid object +convsf_dict['convsf']['data'] = convsf_masked[None,:,:] +# add field +grid.add_field('convsf', convsf_dict['convsf'], replace_existing=True) + +# create plot using GridMapDisplay +# plot variables +display = pyart.graph.GridMapDisplay(grid) +magma_r_cmap = plt.get_cmap('magma_r') +ref_cmap = mcolors.LinearSegmentedColormap.from_list('ref_cmap', magma_r_cmap(np.linspace(0, 0.9, magma_r_cmap.N))) +projection = ccrs.LambertConformal(central_latitude=radar.latitude['data'][0], + central_longitude=radar.longitude['data'][0]) +# plot +plt.figure(figsize=(10,4)) +ax1=plt.subplot(1,2,1, projection=projection) +display.plot_grid('reflectivity', vmin=5, vmax=45, cmap=ref_cmap, projection=projection, + transform=ccrs.PlateCarree(), ax=ax1, axislabels_flag=False) +ax2=plt.subplot(1,2,2, projection=projection) +display.plot_grid('convsf', vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3), projection=projection, + axislabels_flag=False, transform=ccrs.PlateCarree(), ticks=[1/3, 1, 5/3], + ticklabs=['', 'Stratiform', 'Convective'], ax=ax2) +plt.show() + +###################################### +# **Winter storm example with image muting** +# Here is a final example of the convective stratiform classification using an example from a winter storm. Before +# doing the classification, we image mute the reflectivity to remove regions with melting or mixed precipitation. We +# then rescale the reflectivity to snow rate. We recommend using a rescaled reflectivity to do the classification, +# but if you do make sure to changed dB_averaging to False because this parameter is used to convert reflectivity to +# a linear value before averaging (set dB_averaging to True for reflectivity fields in dBZ units). + +# Read in file +nexrad_file = 's3://noaa-nexrad-level2/2021/02/07/KOKX/KOKX20210207_161413_V06' +radar = pyart.io.read_nexrad_archive(nexrad_file) + +# extract the lowest sweep +radar = radar.extract_sweeps([0]) + +# interpolate to grid +grid = pyart.map.grid_from_radars( + (radar,), grid_shape=(1, 201, 201), + grid_limits=((0, 10000), (-200000.0, 200000.0), (-200000.0, 200000.0)), + fields=['reflectivity', 'cross_correlation_ratio']) + +# image mute grid object +grid = pyart.util.image_mute_radar(grid, 'reflectivity', 'cross_correlation_ratio', 0.97, 20) + +# convect non-muted reflectivity to snow rate +nonmuted_ref = grid.fields['nonmuted_reflectivity']['data'][0,:,:] +nonmuted_ref = np.ma.masked_invalid(nonmuted_ref) + +nonmuted_ref_linear = 10 ** (nonmuted_ref / 10) # mm6/m3 +snow_rate = (nonmuted_ref_linear/57.3)**(1/1.67) # + +# add to grid +snow_rate_dict = { + 'data': snow_rate[None,:,:], + 'standard_name': 'snow_rate', + 'long_name': 'Snow rate converted from linear reflectivity', + 'units': 'mm/hr', + 'valid_min': 0, + 'valid_max': 40500} +grid.add_field('snow_rate', snow_rate_dict, replace_existing=True) + +# get dx dy +dx = grid.x['data'][1] - grid.x['data'][0] +dy = grid.y['data'][1] - grid.y['data'][0] + +# convective stratiform classification +convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='snow_rate', dB_averaging=False,always_core_thres=4, + bkg_rad_km=40, use_cosine=True, max_diff=1.5, zero_diff_cos_val=5, + weak_echo_thres=0, min_dBZ_used=0, max_conv_rad_km=1) + +# add to grid object +# mask zero values (no surface echo) +convsf_masked = np.ma.masked_equal(convsf_dict['convsf']['data'], 0) +# mask 3 values (weak echo) +convsf_masked = np.ma.masked_equal(convsf_masked, 3) +# add dimension to array to add to grid object +convsf_dict['convsf']['data'] = convsf_masked[None,:,:] +# add field +grid.add_field('convsf', convsf_dict['convsf'], replace_existing=True) + +# create plot using GridMapDisplay +# plot variables +display = pyart.graph.GridMapDisplay(grid) +magma_r_cmap = plt.get_cmap('magma_r') +ref_cmap = mcolors.LinearSegmentedColormap.from_list('ref_cmap', magma_r_cmap(np.linspace(0, 0.9, magma_r_cmap.N))) +projection = ccrs.LambertConformal(central_latitude=radar.latitude['data'][0], + central_longitude=radar.longitude['data'][0]) +# plot +plt.figure(figsize=(10,4)) +ax1=plt.subplot(1,2,1, projection=projection) +display.plot_grid('snow_rate', vmin=0, vmax=10, cmap=plt.get_cmap('viridis'), projection=projection, + transform=ccrs.PlateCarree(), ax=ax1, axislabels_flag=False) +ax2=plt.subplot(1,2,2, projection=projection) +display.plot_grid('convsf', vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3), projection=projection, + axislabels_flag=False, transform=ccrs.PlateCarree(), ticks=[1/3, 1, 5/3], + ticklabs=['', 'Stratiform', 'Convective'], ax=ax2) +plt.show() + From 9c92ed7e769a457dff97bbb9c90ccef7c9bb27bb Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 4 Nov 2022 13:08:56 -0400 Subject: [PATCH 48/66] Add description --- .../retrieve/plot_convective_stratiform.py | 119 +++++++++++++++--- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/examples/retrieve/plot_convective_stratiform.py b/examples/retrieve/plot_convective_stratiform.py index 2410058bb8..22e87f7bc2 100644 --- a/examples/retrieve/plot_convective_stratiform.py +++ b/examples/retrieve/plot_convective_stratiform.py @@ -1,10 +1,30 @@ """ ======================================= -Convective-Stratiform classification examples +Convective-Stratiform classification ======================================= This example shows how to use the updated convective stratiform classifcation algorithm. We show 3 examples, -a summer convective example, an example from Hurricane Ian, and an example from a winter storm. - +a summer convective example, an example from Hurricane Ian, and an example from a winter storm. + +References +---------- +Steiner, M. R., R. A. Houze Jr., and S. E. Yuter, 1995: Climatological +Characterization of Three-Dimensional Storm Structure from Operational +Radar and Rain Gauge Data. J. Appl. Meteor., 34, 1978-2007. +https://doi.org/10.1175/1520-0450(1995)034<1978:CCOTDS>2.0.CO;2. + +Yuter, S. E., and R. A. Houze, Jr., 1997: Measurements of raindrop size +distributions over the Pacific warm pool and implications for Z-R relations. +J. Appl. Meteor., 36, 847-867. +https://doi.org/10.1175/1520-0450(1997)036%3C0847:MORSDO%3E2.0.CO;2 + +Yuter, S. E., R. A. Houze, Jr., E. A. Smith, T. T. Wilheit, and E. Zipser, +2005: Physical characterization of tropical oceanic convection observed in +KWAJEX. J. Appl. Meteor., 44, 385-415. https://doi.org/10.1175/JAM2206.1 + +Rasmussen, R., M. Dixon, S. Vasiloff, F. Hage, S. Knight, J. Vivekanandan, +and M. Xu, 2003: Snow Nowcasting Using a Real-Time Correlation of Radar +Reflectivity with Snow Gauge Accumulation. J. Appl. Meteorol. Climatol., 42, 20–36. +https://doi.org/10.1175/1520-0450(2003)042%3C0020:SNUART%3E2.0.CO;2 """ print(__doc__) @@ -20,10 +40,67 @@ import cartopy.crs as ccrs ###################################### -# **Example with summer convection** -# +# **How the algorithm works** +# This first section describes how the convective-stratiform algorithm works (see references for full details). This +# algorithm is a feature detection algorithm and classifies fields as "convective" or "stratiform". The algorithm is +# designed to detect features in a reflectivity field but can also detect features in fields such as rain rate or +# snow rate. In this section we describe the steps of the convective stratiform algorithm and the variables used in +# the function. +# The first step of the algorithm calculates a background average of the field with a circular footprint using a radius +# provided by `bkg_rad_km`. A larger radius will yield a smoother field. The radius needs to be at least double the +# grid spacing, but we recommend at least three times the grid spacing. If using reflectivity, `dB_averaging` should be set +# to True to convert reflectivity to linear Z before averaging, False for rescaled fields such as rain or snow rate. +# `calc_thres` determines the minimum fraction of a circle that is considered in the background average calculation +# (default is 0.75, so the points along the edges where there is less than 75% of a full circle of data, +# the algorithm is not run). +# Once the background average has been calculated, the original field is compared to the background average. In +# order for points to be considered "convective cores" they must exceed the background value by a certain value or +# simply be greater than the `always_core_thres`. This value is determined by either a cosine scheme, or a scalar +# value (i.e. the reflectivity value must be X times the background value (multiplier; `use_addition=False`), +# or X greater than the background value (`use_addition=True`) where X is the `scalar_diff`). +# `use_cosine` determines if a cosine scheme or scalar scheme is to be used. If `use_cosine` is True, +# then the `max_diff` and `zero_diff_cos_val` come into use. These values define the cosine scheme that is used to +# determine the minimum difference between the background and reflectivity value in order for a core to be +# identified. `max_diff` is the maximum difference between the field and the background for a core to be identified, +# or where the cosine function crosses the y-axis. The `zero_diff_cos_val` is where the difference between the field +# and the background is zero, or where the cosine function crosses the x-axis. Note, if +# `always_core_thres` < `zero_diff_cos_val`, `zero_diff_cos_val` only helps define the shape of the cosine curve and +# all values greater than `always_core_thres` will be considered a convective core. If +# `always_core_thres` > `zero_diff_cos_val` then all values greater than `zero_diff_cos_val` will be considered a +# convective core. We plot some examples of the schemes below: + +# plot cosine scheme +pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=30, use_cosine=True, max_diff=5, + zero_diff_cos_val=45) +# when zero_diff_cos_val is greater than always_core_thres, the difference becomes zero at the zero_diff_cos_val +pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=55, use_cosine=True, max_diff=5, + zero_diff_cos_val=45) +# alternatively, we can use a simpler scalar difference instead of a cosine scheme +pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, + zero_diff_cos_val=None, use_addition=True, scalar_diff=2) +# if you are interested in picking up weak features, you can also use the scalar difference as a multiplier instead, +# so very weak features do not have to be that different from the background to be classified as convective. +pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, + zero_diff_cos_val=None, use_addition=False, scalar_diff=2) +###################################### +# Once the cores are identified, there is an option to remove speckles (`remove_small_objects`) smaller than a given +# size (`min_km2_size`). +# After the convective cores are identified, We then incorporate convective radii using +# `val_for_max_conv_rad` and `max_conv_rad_km`. The convective radii act as a dilation and are used to classify +# additional points around the cores as convective that may not have been identified previously. The +# `val_for_max_conv_rad` is the value where the maximum convective radius is applied and the `max_conv_rad_km` is the +# maximum convective radius. Values less than the `val_for_max_conv_rad` are assigned a convective radius using a step +# function. +# Finally, the points are classified as NOSFCECHO (threshold set with `min_dBZ_used`; 0), WEAKECHO (threshold set with +# `weak_echo_thres`; 3), SF (stratiform; 1), CONV (convective; 2). +###################################### +# **Classification of summer convective example** +# Our first example classifies echo from a summer convective event. We use a cosine scheme to classify the convective +# points. + +# Now let's do a classification with our parameters # read in file filename = pyart.testing.get_test_data('swx_20120520_0641.nc') radar = pyart.io.read(filename) @@ -64,10 +141,10 @@ projection = ccrs.LambertConformal(central_latitude=radar.latitude['data'][0], central_longitude=radar.longitude['data'][0]) ###################################### -# You'll notice that the convective stratiform field has less data around the edges compared to the reflectivity +# Note how the convective stratiform field has less data around the edges compared to the reflectivity # field. This is because the function is designed to only compute the background radius where the footprint has 75% # of data, so along the edges there is not enough data to fulfill this requirement. The footprint percentage can be -# changed using the variable, calc_thres. +# changed using the variable `calc_thres`. # plot plt.figure(figsize=(10,4)) @@ -81,11 +158,11 @@ ###################################### -# In addition to the default convective-stratiform classification, the function also returns an underestimate ( -# convsf_under) and an overestimate (convsf_over) to take into consideration the uncertainty when choosing +# In addition to the default convective-stratiform classification, the function also returns an underestimate +# (convsf_under) and an overestimate (convsf_over) to take into consideration the uncertainty when choosing # classification parameters. The under and overestimate use the same parameters, but vary the input field by a -# certain value (default is 5 dBZ). The estimation can be turned off (estimate_flag=False), but we recommend keeping -# it turned on. +# certain value (default is 5 dBZ, can be changed with `estimate_offset`). The estimation can be turned off ( +# `estimate_flag=False`), but we recommend keeping it turned on. # mask weak echo and no surface echo convsf_masked = np.ma.masked_equal(convsf_dict['convsf']['data'], 0) @@ -103,7 +180,7 @@ # Plot each estimation plt.figure(figsize=(10,4)) ax1=plt.subplot(131) -ax1.pcolormesh(convsf_dict['convsf']['data'], vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3)) +ax1.pcolormesh(convsf_dict['convsf']['data'][0,:,:], vmin=0, vmax=2, cmap=plt.get_cmap('viridis', 3)) ax1.set_title('Best estimate') ax1.set_aspect('equal') ax2=plt.subplot(132) @@ -140,7 +217,7 @@ # convective stratiform classification convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='reflectivity', always_core_thres=40, bkg_rad_km=20, use_cosine=True, max_diff=3, zero_diff_cos_val=55, - weak_echo_thres=5, max_conv_rad_km=2) + weak_echo_thres=5, max_conv_rad_km=2, estimate_flag=False) # add to grid object # mask zero values (no surface echo) @@ -174,9 +251,12 @@ # **Winter storm example with image muting** # Here is a final example of the convective stratiform classification using an example from a winter storm. Before # doing the classification, we image mute the reflectivity to remove regions with melting or mixed precipitation. We -# then rescale the reflectivity to snow rate. We recommend using a rescaled reflectivity to do the classification, -# but if you do make sure to changed dB_averaging to False because this parameter is used to convert reflectivity to -# a linear value before averaging (set dB_averaging to True for reflectivity fields in dBZ units). +# then rescale the reflectivity to snow rate (Rasumussen et al. 2003). We recommend using a rescaled reflectivity +# to do the classification, but if you do make sure to changed dB_averaging to False because this parameter is used +# to convert reflectivity to a linear value before averaging (set dB_averaging to True for reflectivity fields in +# dBZ units). +# In this example, note how we change some of the other parameters since we are classifying snow rate instead of +# reflecitivity. # Read in file nexrad_file = 's3://noaa-nexrad-level2/2021/02/07/KOKX/KOKX20210207_161413_V06' @@ -218,7 +298,7 @@ # convective stratiform classification convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='snow_rate', dB_averaging=False,always_core_thres=4, bkg_rad_km=40, use_cosine=True, max_diff=1.5, zero_diff_cos_val=5, - weak_echo_thres=0, min_dBZ_used=0, max_conv_rad_km=1) + weak_echo_thres=0, min_dBZ_used=0, max_conv_rad_km=1, estimate_flag=False) # add to grid object # mask zero values (no surface echo) @@ -248,3 +328,8 @@ ticklabs=['', 'Stratiform', 'Convective'], ax=ax2) plt.show() +###################################### +# **Summary of recommendations and best practices** +# - Tune your parameters to your specific purpose +# - Use a rescaled field if possible (i.e. linear reflectivity, rain or snow rate) +# - Keep `estimate_flag=True` to see uncertainty in classification \ No newline at end of file From 6f0eae4666614aaa6bf26145d7c66b48fefa60d1 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 4 Nov 2022 13:09:12 -0400 Subject: [PATCH 49/66] Add plotting function --- pyart/retrieve/_echo_class.py | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index ca00e2e2ec..28f32a7d4f 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -1,5 +1,6 @@ import numpy as np import scipy.ndimage +import matplotlib.pyplot as plt def _steiner_conv_strat(refl, x, y, dx, dy, intense=42, peak_relation=0, area_relation=1, bkg_rad=11000, use_intense=True): @@ -680,3 +681,55 @@ def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, conv_strat_array[refl < MINDBZUSE] = NOSFCECHO return conv_strat_array + +def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff, zero_diff_cos_val, use_addition=False, scalar_diff=None): + + # create array of background values + bkg_vals = np.linspace(0, 60, 100) + + # create difference array + if use_cosine: + # cosine scheme + diff = max_diff * np.cos(np.pi * bkg_vals / (2 * zero_diff_cos_val)) + else: + if use_addition: + # scalar addition scheme + diff = (bkg_vals + scalar_diff) - bkg_vals + else: + # scalar multiplier scheme + diff = (bkg_vals * scalar_diff) - bkg_vals + + # if values are less than zero, set to zero + diff[diff < 0] = 0 + # background values greater than always core thres, set to zero + diff[bkg_vals > always_core_thres] = 0 + + # Now plot + fig = plt.figure() + ax = plt.gca() + # plot difference line + ax.plot(bkg_vals, diff, lw=2, color='black') + # plot always core thres + ax.axvline(x=always_core_thres, lw=1, ls='--', color='red') + ax.text(always_core_thres+2, 1, 'Always Core Thres.', color='red') + if use_cosine: + # plot zero difference cosine value + ax.axvline(x=zero_diff_cos_val, lw=1, ls='--', color='green') + ax.text(zero_diff_cos_val+2, 0.75, 'Zero Diff. Cosine Val.', color='green') + # plot max difference + ax.axhline(y=max_diff, lw=1, ls='--', color='blue') + ax.text(10, max_diff+0.05, 'Max. Diff.', color='blue') + elif use_addition: + # plot scalar + ax.axhline(y=scalar_diff, lw=1, ls='--', color='orange') + ax.text(10, scalar_diff+0.05, 'Scalar Diff.', color='orange') + # add grid + ax.grid() + # set axis limits + ax.set_ylim([0, np.max(diff) + 0.2]) + ax.set_xlim([np.min(bkg_vals), np.max(bkg_vals)]) + # set axis labels and title + ax.set_ylabel('Difference (dBZ - dBZ$_{background}$)') + ax.set_xlabel('Background Value (dBZ$_{background}$)') + ax.set_title('Convective Stratiform Equation') + plt.show() \ No newline at end of file From af5b7d27c811ef07131171869d9ac0bc18ebf716 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 4 Nov 2022 13:09:27 -0400 Subject: [PATCH 50/66] Add DOI --- pyart/retrieve/echo_class.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 5322acd275..c1f7ac0354 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -108,7 +108,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, - weak_echo_thres=5.0, min_dBZ_used=5.0,dB_averaging=True, + weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, refl_field=None, estimate_flag=True, estimate_offset=5): @@ -185,10 +185,11 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r Yuter, S. E., and R. A. Houze, Jr., 1997: Measurements of raindrop size distributions over the Pacific warm pool and implications for Z-R relations. J. Appl. Meteor., 36, 847-867. + https://doi.org/10.1175/1520-0450(1997)036%3C0847:MORSDO%3E2.0.CO;2 Yuter, S. E., R. A. Houze, Jr., E. A. Smith, T. T. Wilheit, and E. Zipser, 2005: Physical characterization of tropical oceanic convection observed in - KWAJEX. J. Appl. Meteor., 44, 385-415. + KWAJEX. J. Appl. Meteor., 44, 385-415. https://doi.org/10.1175/JAM2206.1 """ From c59d1f67a7ab9eabe595c6c6cd6882b014606945 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 4 Nov 2022 13:13:51 -0400 Subject: [PATCH 51/66] Add variable description for plot function --- pyart/retrieve/_echo_class.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index 28f32a7d4f..facbb1d29b 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -682,7 +682,30 @@ def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, return conv_strat_array -def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff, zero_diff_cos_val, use_addition=False, scalar_diff=None): +def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff=None, zero_diff_cos_val=None, + use_addition=False, scalar_diff=None): + """ + Plots the scheme used in the convective stratiform classification + + Parameters + ---------- + always_core_thres : float + All values above this threshold considered to be convective + use_cosine : bool + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar + scheme (False) + max_diff : float, optional + Maximum difference between background average and reflectivity in order to be classified as convective. + "a" value in Eqn. B1 in Yuter and Houze (1997) + zero_diff_cos_val : float, optional + Value where difference between background average and reflectivity is zero in the cosine function + "b" value in Eqn. B1 in Yuter and Houze (1997) + use_addition : bool, optional + Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + scalar_diff : float, optional + If using a scalar difference scheme, this value is the multiplier or addition to the background average + + """ # create array of background values bkg_vals = np.linspace(0, 60, 100) From e726f1fe4835ef6b6cc8db17222833886f63b25d Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Fri, 4 Nov 2022 14:39:57 -0400 Subject: [PATCH 52/66] Delete test.py --- test.py | 181 -------------------------------------------------------- 1 file changed, 181 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index 4b03c19fb8..0000000000 --- a/test.py +++ /dev/null @@ -1,181 +0,0 @@ - -# test file KBGM 10:30 UTC 7 feb 2020 -import pyart -import nexradaws -import numpy as np -from scipy import ndimage -import itertools -import matplotlib.pyplot as plt -import time -import scipy - -conn = nexradaws.NexradAwsInterface() -availscans = conn.get_avail_scans('2020', '02', '07', 'KBGM') -availscans = conn.get_avail_scans('2021', '02', '07', 'KOKX') - -scan = availscans[120] -scan = availscans[122] -path = scan.create_filepath(basepath='s3://noaa-nexrad-level2/', keep_aws_structure=True)[1] -path = path.replace("\\", '/') -radar = pyart.io.read_nexrad_archive(path) - -# extract the lowest -radar = radar.extract_sweeps([0]) - -# interpolate to grid -grid = pyart.map.grid_from_radars( - (radar,), grid_shape=(1, 251, 251), - grid_limits=((0, 10000), (-200000.0, 200000.0), (-200000.0, 200000.0)), - fields=['reflectivity']) - -dx = grid.x['data'][1] - grid.x['data'][0] -dy = grid.y['data'][1] - grid.y['data'][0] - -refl_og = grid.fields['reflectivity']['data'][0,:,:] -refl_og = np.ma.masked_invalid(refl_og) -refl_og = np.ma.masked_less(refl_og, 5) - -refl_linear = 10 ** (refl_og / 10) # mm6/m3 - -snow_rate = (refl_linear/57.3)**(1/1.67) -refl = snow_rate - -refl = refl_og - - -# trying scipy filter -refl_bkg = ndimage.uniform_filter(refl, size=11) # doesn't work because of NaNs -refl_bkg = ndimage.generic_filter(refl, np.nanmean, mode='constant', cval=np.nan, size=11) -refl_bkg = np.ma.masked_where(refl.mask, refl_bkg) - -bkg_mask_bool = bkg_mask_array.astype(bool) -time1 = time.time() -refl_bkg_scipy = ndimage.generic_filter(refl.filled(np.nan), function=np.nanmean, mode='constant', - footprint=bkg_mask_bool, cval=np.nan) -refl_bkg_scipy = np.ma.masked_where(refl.mask, refl_bkg_scipy) -time2 = time.time() -print(time2-time1) - -# Create idealized arrays -refl_test = np.empty_like(refl) -refl_test[:] = 30 -refl_test[115:125,115:125] = 50 - -# create idealized array (gradient grid with gradient background) -# create background gradient -#bkg_gradient = np.linspace(0, 50, np.shape(ref)[0]) -bkg_gradient = np.linspace(0, 50, np.shape(refl)[0]) - -# block background -bkg_gradient = 5 * np.round(bkg_gradient/5) - -# constant background -bkg_gradient = np.empty_like(bkg_gradient) -bkg_gradient[:] = 25 -# repeat array over other dimension -refl_test = np.array(np.shape(refl)[1]*[bkg_gradient]) -# given a 240 x 240 array, let's set the cores (5x5) -core_gradient = np.linspace(25, 50, 5) -dn = int(np.floor(np.shape(refl_test)[0]/5)) -indices = np.arange(int(dn/2), int(dn/2+5*dn), dn) -for ind in itertools.product(indices, indices): - core_ind = np.where(indices==ind[0])[0][0] - refl_test[ind[0]-3:ind[0]+3, ind[1]-3:ind[1]+3] = core_gradient[core_ind] - #ref_test[ind[0], ind[1]] = core_gradient[core_ind] - #print(ind[0]) - -refl = refl_test.T -refl = np.ma.masked_invalid(refl) - -# plot -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(refl, vmin=0, vmax=50) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(ze_bkg, vmin=0, vmax=50) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(conv_core_array, vmin=0, vmax=3) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(conv_strat_array, vmin=0, vmax=2) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(convRadiuskm, vmin=1, vmax=5) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(zeDiff, vmin=0, vmax=50) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(refl, vmin=0, vmax=12) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(ze_bkg_test, vmin=0, vmax=10)#, cmap='magma_r') -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -fig.savefig("Q:\\My Drive\\phd\\winter_storms\\nexrad_stitching\\convsf_imgs\\testing\\ze_bkg_10km_kbgm.png", dpi=600, bbox_inches='tight') -#fig.savefig("Q:\\My Drive\\phd\\winter_storms\\test.png", dpi=800) -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(conv_core_array, vmin=0, vmax=3) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -# plot -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(snow_rate, vmin=0, vmax=10) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(refl_bkg_scipy, vmin=0, vmax=10) -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - -fig = plt.figure() -ax = plt.axes() -cs = ax.pcolormesh(bkg_diff, vmin=-5e-7, vmax=5e-7, cmap='RdBu_r') -cbar = plt.colorbar(cs) -ax.set_aspect('equal') -plt.show() - - From b64da94d4a538b2746715c31d7d00cb8bd77125f Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 11:43:55 -0500 Subject: [PATCH 53/66] Move plotting function to graph --- .../retrieve/plot_convective_stratiform.py | 14 ++-- pyart/graph/__init__.py | 1 + pyart/graph/convstrat_scheme_plot.py | 77 +++++++++++++++++++ pyart/retrieve/_echo_class.py | 76 ------------------ 4 files changed, 84 insertions(+), 84 deletions(-) create mode 100644 pyart/graph/convstrat_scheme_plot.py diff --git a/examples/retrieve/plot_convective_stratiform.py b/examples/retrieve/plot_convective_stratiform.py index 22e87f7bc2..1cd3963cba 100644 --- a/examples/retrieve/plot_convective_stratiform.py +++ b/examples/retrieve/plot_convective_stratiform.py @@ -70,18 +70,16 @@ # convective core. We plot some examples of the schemes below: # plot cosine scheme -pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=30, use_cosine=True, max_diff=5, - zero_diff_cos_val=45) +pyart.graph.plot_convstrat_scheme(always_core_thres=30, use_cosine=True, max_diff=5, zero_diff_cos_val=45) # when zero_diff_cos_val is greater than always_core_thres, the difference becomes zero at the zero_diff_cos_val -pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=55, use_cosine=True, max_diff=5, - zero_diff_cos_val=45) +pyart.graph.plot_convstrat_scheme(always_core_thres=55, use_cosine=True, max_diff=5, zero_diff_cos_val=45) # alternatively, we can use a simpler scalar difference instead of a cosine scheme -pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, - zero_diff_cos_val=None, use_addition=True, scalar_diff=2) +pyart.graph.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, + zero_diff_cos_val=None, use_addition=True, scalar_diff=2) # if you are interested in picking up weak features, you can also use the scalar difference as a multiplier instead, # so very weak features do not have to be that different from the background to be classified as convective. -pyart.retrieve._echo_class.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, - zero_diff_cos_val=None, use_addition=False, scalar_diff=2) +pyart.graph.plot_convstrat_scheme(always_core_thres=40, use_cosine=False, max_diff=None, + zero_diff_cos_val=None, use_addition=False, scalar_diff=2) ###################################### # Once the cores are identified, there is an option to remove speckles (`remove_small_objects`) smaller than a given diff --git a/pyart/graph/__init__.py b/pyart/graph/__init__.py index 48df69eb71..00bc231bfd 100644 --- a/pyart/graph/__init__.py +++ b/pyart/graph/__init__.py @@ -60,5 +60,6 @@ from .gridmapdisplay_basemap import GridMapDisplayBasemap from .radarmapdisplay import RadarMapDisplay from .radarmapdisplay_basemap import RadarMapDisplayBasemap +from .convstrat_scheme_plot import plot_convstrat_scheme __all__ = [s for s in dir() if not s.startswith('_')] diff --git a/pyart/graph/convstrat_scheme_plot.py b/pyart/graph/convstrat_scheme_plot.py new file mode 100644 index 0000000000..e9db84dc02 --- /dev/null +++ b/pyart/graph/convstrat_scheme_plot.py @@ -0,0 +1,77 @@ +import numpy as np +import matplotlib.pyplot as plt + +def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff=None, zero_diff_cos_val=None, + use_addition=False, scalar_diff=None): + """ + Plots the scheme used in the convective stratiform classification + + Parameters + ---------- + always_core_thres : float + All values above this threshold considered to be convective + use_cosine : bool + Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar + scheme (False) + max_diff : float, optional + Maximum difference between background average and reflectivity in order to be classified as convective. + "a" value in Eqn. B1 in Yuter and Houze (1997) + zero_diff_cos_val : float, optional + Value where difference between background average and reflectivity is zero in the cosine function + "b" value in Eqn. B1 in Yuter and Houze (1997) + use_addition : bool, optional + Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used + scalar_diff : float, optional + If using a scalar difference scheme, this value is the multiplier or addition to the background average + + """ + + # create array of background values + bkg_vals = np.linspace(0, 60, 100) + + # create difference array + if use_cosine: + # cosine scheme + diff = max_diff * np.cos(np.pi * bkg_vals / (2 * zero_diff_cos_val)) + else: + if use_addition: + # scalar addition scheme + diff = (bkg_vals + scalar_diff) - bkg_vals + else: + # scalar multiplier scheme + diff = (bkg_vals * scalar_diff) - bkg_vals + + # if values are less than zero, set to zero + diff[diff < 0] = 0 + # background values greater than always core thres, set to zero + diff[bkg_vals > always_core_thres] = 0 + + # Now plot + fig = plt.figure() + ax = plt.gca() + # plot difference line + ax.plot(bkg_vals, diff, lw=2, color='black') + # plot always core thres + ax.axvline(x=always_core_thres, lw=1, ls='--', color='red') + ax.text(always_core_thres+2, 1, 'Always Core Thres.', color='red') + if use_cosine: + # plot zero difference cosine value + ax.axvline(x=zero_diff_cos_val, lw=1, ls='--', color='green') + ax.text(zero_diff_cos_val+2, 0.75, 'Zero Diff. Cosine Val.', color='green') + # plot max difference + ax.axhline(y=max_diff, lw=1, ls='--', color='blue') + ax.text(10, max_diff+0.05, 'Max. Diff.', color='blue') + elif use_addition: + # plot scalar + ax.axhline(y=scalar_diff, lw=1, ls='--', color='orange') + ax.text(10, scalar_diff+0.05, 'Scalar Diff.', color='orange') + # add grid + ax.grid() + # set axis limits + ax.set_ylim([0, np.max(diff) + 0.2]) + ax.set_xlim([np.min(bkg_vals), np.max(bkg_vals)]) + # set axis labels and title + ax.set_ylabel('Difference (dBZ - dBZ$_{background}$)') + ax.set_xlabel('Background Value (dBZ$_{background}$)') + ax.set_title('Convective Stratiform Equation') + plt.show() \ No newline at end of file diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index facbb1d29b..ca00e2e2ec 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -1,6 +1,5 @@ import numpy as np import scipy.ndimage -import matplotlib.pyplot as plt def _steiner_conv_strat(refl, x, y, dx, dy, intense=42, peak_relation=0, area_relation=1, bkg_rad=11000, use_intense=True): @@ -681,78 +680,3 @@ def classify_conv_strat_array(refl, conv_strat_array, conv_core_array, conv_strat_array[refl < MINDBZUSE] = NOSFCECHO return conv_strat_array - -def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff=None, zero_diff_cos_val=None, - use_addition=False, scalar_diff=None): - """ - Plots the scheme used in the convective stratiform classification - - Parameters - ---------- - always_core_thres : float - All values above this threshold considered to be convective - use_cosine : bool - Boolean used to determine if cosine scheme should be used for identifying convective cores (True) or a scalar - scheme (False) - max_diff : float, optional - Maximum difference between background average and reflectivity in order to be classified as convective. - "a" value in Eqn. B1 in Yuter and Houze (1997) - zero_diff_cos_val : float, optional - Value where difference between background average and reflectivity is zero in the cosine function - "b" value in Eqn. B1 in Yuter and Houze (1997) - use_addition : bool, optional - Determines if a multiplier (False) or addition (True) in the scalar difference scheme should be used - scalar_diff : float, optional - If using a scalar difference scheme, this value is the multiplier or addition to the background average - - """ - - # create array of background values - bkg_vals = np.linspace(0, 60, 100) - - # create difference array - if use_cosine: - # cosine scheme - diff = max_diff * np.cos(np.pi * bkg_vals / (2 * zero_diff_cos_val)) - else: - if use_addition: - # scalar addition scheme - diff = (bkg_vals + scalar_diff) - bkg_vals - else: - # scalar multiplier scheme - diff = (bkg_vals * scalar_diff) - bkg_vals - - # if values are less than zero, set to zero - diff[diff < 0] = 0 - # background values greater than always core thres, set to zero - diff[bkg_vals > always_core_thres] = 0 - - # Now plot - fig = plt.figure() - ax = plt.gca() - # plot difference line - ax.plot(bkg_vals, diff, lw=2, color='black') - # plot always core thres - ax.axvline(x=always_core_thres, lw=1, ls='--', color='red') - ax.text(always_core_thres+2, 1, 'Always Core Thres.', color='red') - if use_cosine: - # plot zero difference cosine value - ax.axvline(x=zero_diff_cos_val, lw=1, ls='--', color='green') - ax.text(zero_diff_cos_val+2, 0.75, 'Zero Diff. Cosine Val.', color='green') - # plot max difference - ax.axhline(y=max_diff, lw=1, ls='--', color='blue') - ax.text(10, max_diff+0.05, 'Max. Diff.', color='blue') - elif use_addition: - # plot scalar - ax.axhline(y=scalar_diff, lw=1, ls='--', color='orange') - ax.text(10, scalar_diff+0.05, 'Scalar Diff.', color='orange') - # add grid - ax.grid() - # set axis limits - ax.set_ylim([0, np.max(diff) + 0.2]) - ax.set_xlim([np.min(bkg_vals), np.max(bkg_vals)]) - # set axis labels and title - ax.set_ylabel('Difference (dBZ - dBZ$_{background}$)') - ax.set_xlabel('Background Value (dBZ$_{background}$)') - ax.set_title('Convective Stratiform Equation') - plt.show() \ No newline at end of file From 21923fcaf60f7afde09c8cc723fd1ceab153450f Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 11:57:17 -0500 Subject: [PATCH 54/66] Delete plot_rhi_sigmet.py --- examples/plotting/plot_rhi_sigmet.py | 45 ---------------------------- 1 file changed, 45 deletions(-) delete mode 100644 examples/plotting/plot_rhi_sigmet.py diff --git a/examples/plotting/plot_rhi_sigmet.py b/examples/plotting/plot_rhi_sigmet.py deleted file mode 100644 index dd9eb1495c..0000000000 --- a/examples/plotting/plot_rhi_sigmet.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -==================================== -Create a RHI plot from a Sigmet file -==================================== - -An example which creates a RHI plot of a Sigmet file using a RadarDisplay -object. - -""" -print(__doc__) - -# Author: Jonathan J. Helmus (jhelmus@anl.gov) -# License: BSD 3 clause - -import matplotlib.pyplot as plt -import pyart -from pyart.testing import get_test_data -import netCDF4 - -filename = get_test_data('XSW110520113537.RAW7HHL') - -# create the plot using RadarDisplay -radar = pyart.io.read_rsl(filename) -display = pyart.graph.RadarDisplay(radar) - -fig = plt.figure(figsize=[10, 4]) -ax = fig.add_subplot(111) - -instrument_name = radar.metadata['instrument_name'].decode('utf-8') -time_start = netCDF4.num2date( - radar.time['data'][0], radar.time['units'], - only_use_cftime_datetimes=False, only_use_python_datetimes=True) -time_text = ' ' + time_start.isoformat() + 'Z' -azimuth = radar.fixed_angle['data'][0] -title = 'RHI ' + instrument_name + time_text + 'Azimuth %.2f' % (azimuth) - -display.plot('reflectivity', 0, vmin=-32, vmax=64, - title=title, colorbar_flag=False, ax=ax) -display.set_limits(ylim=[0, 17]) - -cax = fig.add_axes([.9, .1, 0.02, .8]) -colorbar_label = 'Eq ref fact (dBz)' -display.plot_colorbar(fig=fig, cax=cax, label=colorbar_label) - -plt.show() From 95117b24b61bea80ceacc0eda072540ce14da90c Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 12:03:43 -0500 Subject: [PATCH 55/66] Remove hard coding of constants --- pyart/retrieve/_echo_class.py | 28 ++++++++++++++++------------ pyart/retrieve/echo_class.py | 34 +++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index ca00e2e2ec..b4247ec13f 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -220,17 +220,18 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, - val_for_max_conv_rad=30, max_conv_rad_km=5.0): + val_for_max_conv_rad=30, max_conv_rad_km=5.0, + CS_CORE=3, NOSFCECHO=0, WEAKECHO=3, SF=1, CONV=2): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point as either convective, stratiform or undefined. Grid points are classified as follows, - 0 = No Surface Echo/ Undefined - 1 = Stratiform - 2 = Convective - 3 = Weak Echo + NOSFCECHO = No Surface Echo/ Undefined + SF = Stratiform + CONV = Convective + WEAKECHO = Weak Echo refl : array array of reflectivity values @@ -274,6 +275,16 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km. + CS_CORE : int, optional + Value for points classified as convective cores + NOSFCECHO : int, optional + Value for points classified as no surface echo, based on min_dBZ_used + WEAKECHO : int, optional + Value for points classified as weak echo, based on weak_echo_thres + SF : int, optional + Value for points classified as stratiform + CONV : int, optional + Value for points classified as convective Returns ------- @@ -285,13 +296,6 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, Array of convective stratiform classifcation with convective radii applied """ - # Constants to fill arrays with - CS_CORE = 3 - NOSFCECHO = 0 - WEAKECHO = 3 - SF = 1 - CONV = 2 - # Set up mask arrays for background average and # prepare for convective mask arrays # calculate maximum convective diameter from max. convective radius (input) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 3cac273552..33ffe98f07 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -111,6 +111,7 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, + CS_CORE=3, NOSFCECHO=0, WEAKECHO=3, SF=1, CONV=2, refl_field=None, estimate_flag=True, estimate_offset=5): """ Partition reflectivity into convective-stratiform using the Yuter et al. (2005) @@ -164,6 +165,16 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km + CS_CORE : int, optional + Value for points classified as convective cores + NOSFCECHO : int, optional + Value for points classified as no surface echo, based on min_dBZ_used + WEAKECHO : int, optional + Value for points classified as weak echo, based on weak_echo_thres + SF : int, optional + Value for points classified as stratiform + CONV : int, optional + Value for points classified as convective refl_field : str, optional Field in grid to use as the reflectivity during partitioning. None will use the default reflectivity field name from the Py-ART configuration file. @@ -233,9 +244,10 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, - remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, - val_for_max_conv_rad=val_for_max_conv_rad, max_conv_rad_km=max_conv_rad_km) + dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, + min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, NOSFCECHO=NOSFCECHO, + WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) # put data into a dictionary to be added as a field convsf_dict = {'convsf': { @@ -257,20 +269,20 @@ def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_r zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, - remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, - val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad_km=max_conv_rad_km) + dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, + min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, + NOSFCECHO=NOSFCECHO, WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) _, _, convsf_over = _revised_conv_strat(ze + estimate_offset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, zero_diff_cos_val=zero_diff_cos_val, scalar_diff=scalar_diff, use_addition=use_addition, calc_thres=calc_thres, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, - dB_averaging=dB_averaging, - remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, - val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad_km=max_conv_rad_km) + dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, + min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, + max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, + NOSFCECHO=NOSFCECHO, WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) # save into dictionaries convsf_dict['convsf_under'] = { From c1400d2471343cf5696e20f8211ee524271b1cf5 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 14:17:31 -0500 Subject: [PATCH 56/66] Rename function to conv_strat_yuter --- pyart/retrieve/__init__.py | 2 +- pyart/retrieve/echo_class.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyart/retrieve/__init__.py b/pyart/retrieve/__init__.py index 40531e1641..f84188af5d 100644 --- a/pyart/retrieve/__init__.py +++ b/pyart/retrieve/__init__.py @@ -4,7 +4,7 @@ """ from .kdp_proc import kdp_maesaka, kdp_schneebeli, kdp_vulpiani -from .echo_class import steiner_conv_strat, conv_strat, hydroclass_semisupervised +from .echo_class import steiner_conv_strat, conv_strat_yuter, hydroclass_semisupervised from .echo_class import get_freq_band from .gate_id import map_profile_to_gates, fetch_radar_time_profile from .simple_moment_calculations import calculate_snr_from_reflectivity diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 33ffe98f07..574e838990 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -105,7 +105,7 @@ def steiner_conv_strat(grid, dx=None, dy=None, intense=42.0, '2 = Convective')} -def conv_strat(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, +def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, bkg_rad_km=11, use_cosine=True, max_diff=5, zero_diff_cos_val=55, scalar_diff=1.5, use_addition=True, calc_thres=0.75, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, From e815f47bf49ebc372286cba0a8cedf94a16245e4 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 14:18:11 -0500 Subject: [PATCH 57/66] Update level_m description --- pyart/retrieve/echo_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 574e838990..580ec0d55e 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -125,7 +125,7 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, The x- and y-dimension resolutions in meters, respectively. If None the resolution is determined from the first two axes values parsed from grid object. level_m : float, optional - Desired height to classify with convective stratiform algorithm. + Desired height in meters to classify with convective stratiform algorithm. always_core_thres : float, optional Threshold for points that are always convective. All values above the threshold are classifed as convective See Yuter et al. (2005) for more detail. From bc1de5ad6976783fa9596c1e40a8f18bca886aa2 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 14:21:22 -0500 Subject: [PATCH 58/66] Make constants lowercase --- pyart/retrieve/_echo_class.py | 28 ++++++++++++++-------------- pyart/retrieve/echo_class.py | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pyart/retrieve/_echo_class.py b/pyart/retrieve/_echo_class.py index b4247ec13f..2f6d2a8e2e 100644 --- a/pyart/retrieve/_echo_class.py +++ b/pyart/retrieve/_echo_class.py @@ -221,17 +221,17 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, - CS_CORE=3, NOSFCECHO=0, WEAKECHO=3, SF=1, CONV=2): + cs_core=3, nosfcecho=0, weakecho=3, sf=1, conv=2): """ We perform the Yuter and Houze (1997) algorithm for echo classification using only the reflectivity field in order to classify each grid point as either convective, stratiform or undefined. Grid points are classified as follows, - NOSFCECHO = No Surface Echo/ Undefined - SF = Stratiform - CONV = Convective - WEAKECHO = Weak Echo + nosfcecho = No Surface Echo/ Undefined + sf = Stratiform + conv = Convective + weakecho = Weak Echo refl : array array of reflectivity values @@ -275,15 +275,15 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km. - CS_CORE : int, optional + cs_core : int, optional Value for points classified as convective cores - NOSFCECHO : int, optional + nosfcecho : int, optional Value for points classified as no surface echo, based on min_dBZ_used - WEAKECHO : int, optional + weakecho : int, optional Value for points classified as weak echo, based on weak_echo_thres - SF : int, optional + sf : int, optional Value for points classified as stratiform - CONV : int, optional + conv : int, optional Value for points classified as convective Returns @@ -330,9 +330,9 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, # Get convective core array from cosine scheme, or scalar scheme if use_cosine: - conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, CS_CORE) + conv_core_array = convcore_cos_scheme(refl, refl_bkg, max_diff, zero_diff_cos_val, always_core_thres, cs_core) else: - conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, CS_CORE, + conv_core_array = convcore_scalar_scheme(refl, refl_bkg, scalar_diff, always_core_thres, cs_core, use_addition=use_addition) # Assign convective radii based on background reflectivity @@ -375,12 +375,12 @@ def _revised_conv_strat(refl, dx, dy, always_core_thres=42, bkg_rad_km=11, # add dilated cores to original array conv_core_copy = np.ma.copy(conv_core_array) - conv_core_copy[temp_assignment >= 1] = CS_CORE + conv_core_copy[temp_assignment >= 1] = cs_core # Now do convective stratiform classification conv_strat_array = np.zeros_like(refl) conv_strat_array = classify_conv_strat_array(refl, conv_strat_array, conv_core_copy, - NOSFCECHO, CONV, SF, WEAKECHO, CS_CORE, + nosfcecho, conv, sf, weakecho, cs_core, min_dBZ_used, weak_echo_thres) # mask where reflectivity is masked conv_strat_array = np.ma.masked_where(refl.mask, conv_strat_array) diff --git a/pyart/retrieve/echo_class.py b/pyart/retrieve/echo_class.py index 580ec0d55e..ead3fcbe07 100644 --- a/pyart/retrieve/echo_class.py +++ b/pyart/retrieve/echo_class.py @@ -111,7 +111,7 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, weak_echo_thres=5.0, min_dBZ_used=5.0, dB_averaging=True, remove_small_objects=True, min_km2_size=10, val_for_max_conv_rad=30, max_conv_rad_km=5.0, - CS_CORE=3, NOSFCECHO=0, WEAKECHO=3, SF=1, CONV=2, + cs_core=3, nosfcecho=0, weakecho=3, sf=1, conv=2, refl_field=None, estimate_flag=True, estimate_offset=5): """ Partition reflectivity into convective-stratiform using the Yuter et al. (2005) @@ -165,15 +165,15 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, radius max_conv_rad_km : float, optional Maximum radius around convective cores to classify as convective. Default is 5 km - CS_CORE : int, optional + cs_core : int, optional Value for points classified as convective cores - NOSFCECHO : int, optional + nosfcecho : int, optional Value for points classified as no surface echo, based on min_dBZ_used - WEAKECHO : int, optional + weakecho : int, optional Value for points classified as weak echo, based on weak_echo_thres - SF : int, optional + sf : int, optional Value for points classified as stratiform - CONV : int, optional + conv : int, optional Value for points classified as convective refl_field : str, optional Field in grid to use as the reflectivity during partitioning. None will use the default reflectivity @@ -246,8 +246,8 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, NOSFCECHO=NOSFCECHO, - WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) + max_conv_rad_km=max_conv_rad_km, cs_core=cs_core, nosfcecho=nosfcecho, + weakecho=weakecho, sf=sf, conv=conv) # put data into a dictionary to be added as a field convsf_dict = {'convsf': { @@ -271,8 +271,8 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, - NOSFCECHO=NOSFCECHO, WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) + max_conv_rad_km=max_conv_rad_km, cs_core=cs_core, nosfcecho=nosfcecho, + weakecho=weakecho, sf=sf, conv=conv) _, _, convsf_over = _revised_conv_strat(ze + estimate_offset, dx, dy, always_core_thres=always_core_thres, bkg_rad_km=bkg_rad_km, use_cosine=use_cosine, max_diff=max_diff, @@ -281,8 +281,8 @@ def conv_strat_yuter(grid, dx=None, dy=None, level_m=None, always_core_thres=42, weak_echo_thres=weak_echo_thres, min_dBZ_used=min_dBZ_used, dB_averaging=dB_averaging, remove_small_objects=remove_small_objects, min_km2_size=min_km2_size, val_for_max_conv_rad=val_for_max_conv_rad, - max_conv_rad_km=max_conv_rad_km, CS_CORE=CS_CORE, - NOSFCECHO=NOSFCECHO, WEAKECHO=WEAKECHO, SF=SF, CONV=CONV) + max_conv_rad_km=max_conv_rad_km, cs_core=cs_core, nosfcecho=nosfcecho, + weakecho=weakecho, sf=sf, conv=conv) # save into dictionaries convsf_dict['convsf_under'] = { From 53292fada29b99516b901944277902ccefa20ec5 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Tue, 8 Nov 2022 14:21:46 -0500 Subject: [PATCH 59/66] Remove plt.show() --- pyart/graph/convstrat_scheme_plot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyart/graph/convstrat_scheme_plot.py b/pyart/graph/convstrat_scheme_plot.py index e9db84dc02..3001480dac 100644 --- a/pyart/graph/convstrat_scheme_plot.py +++ b/pyart/graph/convstrat_scheme_plot.py @@ -73,5 +73,4 @@ def plot_convstrat_scheme(always_core_thres, use_cosine, max_diff=None, zero_dif # set axis labels and title ax.set_ylabel('Difference (dBZ - dBZ$_{background}$)') ax.set_xlabel('Background Value (dBZ$_{background}$)') - ax.set_title('Convective Stratiform Equation') - plt.show() \ No newline at end of file + ax.set_title('Convective Stratiform Equation') \ No newline at end of file From dc3a7803caf9f90954def4b03b65158ce676f0aa Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 11:07:33 -0500 Subject: [PATCH 60/66] Update function name in example --- .../retrieve/plot_convective_stratiform.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/retrieve/plot_convective_stratiform.py b/examples/retrieve/plot_convective_stratiform.py index 1cd3963cba..b4a33b45de 100644 --- a/examples/retrieve/plot_convective_stratiform.py +++ b/examples/retrieve/plot_convective_stratiform.py @@ -117,9 +117,9 @@ dy = grid.y['data'][1] - grid.y['data'][0] # convective stratiform classification -convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='reflectivity_horizontal', always_core_thres=40, - bkg_rad_km=20, use_cosine=True, max_diff=5, zero_diff_cos_val=55, - weak_echo_thres=10, max_conv_rad_km=2) +convsf_dict = pyart.retrieve.conv_strat_yuter(grid, dx, dy, refl_field='reflectivity_horizontal', always_core_thres=40, + bkg_rad_km=20, use_cosine=True, max_diff=5, zero_diff_cos_val=55, + weak_echo_thres=10, max_conv_rad_km=2) # add to grid object # mask zero values (no surface echo) @@ -213,9 +213,9 @@ dy = grid.y['data'][1] - grid.y['data'][0] # convective stratiform classification -convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='reflectivity', always_core_thres=40, - bkg_rad_km=20, use_cosine=True, max_diff=3, zero_diff_cos_val=55, - weak_echo_thres=5, max_conv_rad_km=2, estimate_flag=False) +convsf_dict = pyart.retrieve.conv_strat_yuter(grid, dx, dy, refl_field='reflectivity', always_core_thres=40, + bkg_rad_km=20, use_cosine=True, max_diff=3, zero_diff_cos_val=55, + weak_echo_thres=5, max_conv_rad_km=2, estimate_flag=False) # add to grid object # mask zero values (no surface echo) @@ -294,9 +294,10 @@ dy = grid.y['data'][1] - grid.y['data'][0] # convective stratiform classification -convsf_dict = pyart.retrieve.conv_strat(grid, dx, dy, refl_field='snow_rate', dB_averaging=False,always_core_thres=4, - bkg_rad_km=40, use_cosine=True, max_diff=1.5, zero_diff_cos_val=5, - weak_echo_thres=0, min_dBZ_used=0, max_conv_rad_km=1, estimate_flag=False) +convsf_dict = pyart.retrieve.conv_strat_yuter(grid, dx, dy, refl_field='snow_rate', dB_averaging=False, + always_core_thres=4,bkg_rad_km=40, use_cosine=True, max_diff=1.5, + zero_diff_cos_val=5,weak_echo_thres=0, min_dBZ_used=0, + max_conv_rad_km=1, estimate_flag=False) # add to grid object # mask zero values (no surface echo) From 6d1aec1b1008df5f138dc5e65c30b4500a6b715d Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:22:07 -0500 Subject: [PATCH 61/66] Update test_echo_class.py --- tests/retrieve/test_echo_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/retrieve/test_echo_class.py b/tests/retrieve/test_echo_class.py index 06030a4042..b9eccedd6a 100644 --- a/tests/retrieve/test_echo_class.py +++ b/tests/retrieve/test_echo_class.py @@ -22,9 +22,9 @@ def test_steiner_conv_strat_modify_area(area_relation): assert eclass['data'].min() == 0 assert eclass['data'].max() == 2 -def test_conv_strat_default(): +def test_conv_strat__yuter_default(): grid = pyart.testing.make_storm_grid() - dict = pyart.retrieve.conv_strat(grid, bkg_rad_km=50) + dict = pyart.retrieve.conv_strat_yuter(grid, bkg_rad_km=50) assert 'convsf' in dict.keys() assert 'convsf_under' in dict.keys() @@ -33,9 +33,9 @@ def test_conv_strat_default(): [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0])) -def test_conv_strat_noest(): +def test_conv_strat_yuter_noest(): grid = pyart.testing.make_storm_grid() - dict = pyart.retrieve.conv_strat(grid, bkg_rad_km=50, estimate_flag=False) + dict = pyart.retrieve.conv_strat_yuter(grid, bkg_rad_km=50, estimate_flag=False) assert 'convsf' in dict.keys() assert 'convsf_under' not in dict.keys() From 227e2455a5edf19844a28682c449801f2cd78947 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:36:56 -0500 Subject: [PATCH 62/66] Update tests/io/test_mdv_grid.py Co-authored-by: Max Grover --- tests/io/test_mdv_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/test_mdv_grid.py b/tests/io/test_mdv_grid.py index 8ca30cfea5..c97a6fe302 100644 --- a/tests/io/test_mdv_grid.py +++ b/tests/io/test_mdv_grid.py @@ -213,7 +213,7 @@ def test_mdv_degree_grid(): pyart.testing.MDV_GRID_FILE, file_field_names=True) assert 'ref' in grid.fields.keys() - fdata = grid.fields['ref']['data'] + fdata = grid.fields['refl']['data'] assert fdata.shape == (1, 1837, 3661) assert np.ma.is_masked(fdata[0, 0, 0]) assert_almost_equal(fdata[0, 130, 2536], 20.0, 1) From 13284eecf79b2f4bb58fd9d29a5e0e8722a13196 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:47:03 -0500 Subject: [PATCH 63/66] Update test_mdv_grid.py --- tests/io/test_mdv_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/test_mdv_grid.py b/tests/io/test_mdv_grid.py index c97a6fe302..a290aa8791 100644 --- a/tests/io/test_mdv_grid.py +++ b/tests/io/test_mdv_grid.py @@ -212,7 +212,7 @@ def test_mdv_degree_grid(): grid = pyart.io.read_grid_mdv( pyart.testing.MDV_GRID_FILE, file_field_names=True) - assert 'ref' in grid.fields.keys() + assert 'refl' in grid.fields.keys() fdata = grid.fields['refl']['data'] assert fdata.shape == (1, 1837, 3661) assert np.ma.is_masked(fdata[0, 0, 0]) From 3848cb1d48aa8376eb0b3a5c297e8efc08e23d4f Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:47:38 -0500 Subject: [PATCH 64/66] Update custom_config.py --- tests/custom_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/custom_config.py b/tests/custom_config.py index 4290691fee..bcb999c718 100644 --- a/tests/custom_config.py +++ b/tests/custom_config.py @@ -595,9 +595,9 @@ 'HCLASS': radar_echo_classification, # (55) Hydrometeor class 'HCLASS2': radar_echo_classification, # (56) Hydrometeor class 'ZDRC': corrected_differential_reflectivity, - # (57) Corrected diff. ref. + # (57) Corrected diff. refl. 'ZDRC2': corrected_differential_reflectivity, - # (58) Corrected diff. ref. + # (58) Corrected diff. refl. } From a34a7cae3088fbb548d52d15295928d519558b40 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:49:07 -0500 Subject: [PATCH 65/66] Update default_config.py --- pyart/default_config.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyart/default_config.py b/pyart/default_config.py index bd86addb32..6b1cb27222 100644 --- a/pyart/default_config.py +++ b/pyart/default_config.py @@ -1024,9 +1024,9 @@ 'HCLASS': radar_echo_classification, # (55) Hydrometeor class 'HCLASS2': radar_echo_classification, # (56) Hydrometeor class 'ZDRC': corrected_differential_reflectivity, - # (57) Corrected diff. ref. + # (57) Corrected diff. refl. 'ZDRC2': corrected_differential_reflectivity, - # (58) Corrected diff. ref. + # (58) Corrected diff. refl. 'UNKNOWN_59': None, # Unknown field 'UNKNOWN_60': None, # Unknown field 'UNKNOWN_61': None, # Unknown field @@ -1319,11 +1319,11 @@ 'ZDR': corrected_differential_reflectivity, # Differential reflectivity from corrected timeseries 'UZDR': differential_reflectivity, - # Diff. ref. from uncorrected timeseries - 'AZDR': None, # Diff. ref., rainfall atten. corr., corr t.s. - 'ZDR1': None, # Diff. ref., corr. t.s., 1st LAG algo. - 'UZDR1': None, # Diff. ref., uncorr. t.s., 1st LAG algo. - 'AZDR1': None, # Diff. ref., rain. atten. corr., corr. t.s., 1st LAG + # Diff. refl. from uncorrected timeseries + 'AZDR': None, # Diff. refl., rainfall atten. corr., corr t.s. + 'ZDR1': None, # Diff. refl., corr. t.s., 1st LAG algo. + 'UZDR1': None, # Diff. refl., uncorr. t.s., 1st LAG algo. + 'AZDR1': None, # Diff. refl., rain. atten. corr., corr. t.s., 1st LAG 'PHI': corrected_differential_phase, # Differential phase from corrected timeseries From f7f68297c306694db164788b88011773a7041126 Mon Sep 17 00:00:00 2001 From: lauratomkins Date: Wed, 9 Nov 2022 12:50:00 -0500 Subject: [PATCH 66/66] Update attenuation.py --- pyart/correct/attenuation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyart/correct/attenuation.py b/pyart/correct/attenuation.py index 057c8f18ae..d1c9f6ab15 100644 --- a/pyart/correct/attenuation.py +++ b/pyart/correct/attenuation.py @@ -37,7 +37,7 @@ def calculate_attenuation_zphi(radar, doc=None, fzl=None, smooth_window_len=5, ---------- radar : Radar Radar object to use for attenuation calculations. Must have - phidp and ref fields. + phidp and refl fields. doc : float, optional Number of gates at the end of each ray to to remove from the calculation. @@ -243,7 +243,7 @@ def calculate_attenuation_zphi(radar, doc=None, fzl=None, smooth_window_len=5, if end_gate_arr[ray] > smooth_window_len: # extract the ray's phase shift, - # init. ref. correction and mask + # init. refl. correction and mask ray_phase_shift = corr_phidp[ray, 0:end_gate_arr[ray]] ray_mask = mask[ray, 0:end_gate_arr[ray]] ray_refl_linear = refl_linear[ray, 0:end_gate_arr[ray]] @@ -335,7 +335,7 @@ def calculate_attenuation_philinear( ---------- radar : Radar Radar object to use for attenuation calculations. Must have - phidp and ref fields. + phidp and refl fields. doc : float, optional Number of gates at the end of each ray to to remove from the calculation. @@ -922,7 +922,7 @@ def calculate_attenuation(radar, z_offset, debug=False, doc=15, fzl=4000.0, for i in range(start_ray, end_ray): # perform attenuation calculation on a single ray - # extract the ray's phase shift and init. ref. correction + # extract the ray's phase shift and init. refl. correction ray_phase_shift = proc_dp_phase_shift[i, 0:end_gate] ray_init_refl = init_refl_correct[i, 0:end_gate]