disdrodb 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- disdrodb/__init__.py +68 -34
- disdrodb/_config.py +5 -4
- disdrodb/_version.py +16 -3
- disdrodb/accessor/__init__.py +20 -0
- disdrodb/accessor/methods.py +125 -0
- disdrodb/api/checks.py +177 -24
- disdrodb/api/configs.py +3 -3
- disdrodb/api/info.py +13 -13
- disdrodb/api/io.py +281 -22
- disdrodb/api/path.py +184 -195
- disdrodb/api/search.py +18 -9
- disdrodb/cli/disdrodb_create_summary.py +103 -0
- disdrodb/cli/disdrodb_create_summary_station.py +91 -0
- disdrodb/cli/disdrodb_run_l0.py +1 -1
- disdrodb/cli/disdrodb_run_l0_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0a_station.py +1 -1
- disdrodb/cli/disdrodb_run_l0b.py +1 -1
- disdrodb/cli/disdrodb_run_l0b_station.py +3 -3
- disdrodb/cli/disdrodb_run_l0c.py +1 -1
- disdrodb/cli/disdrodb_run_l0c_station.py +3 -3
- disdrodb/cli/disdrodb_run_l1_station.py +2 -2
- disdrodb/cli/disdrodb_run_l2e_station.py +2 -2
- disdrodb/cli/disdrodb_run_l2m_station.py +2 -2
- disdrodb/configs.py +149 -4
- disdrodb/constants.py +61 -0
- disdrodb/data_transfer/download_data.py +127 -11
- disdrodb/etc/configs/attributes.yaml +339 -0
- disdrodb/etc/configs/encodings.yaml +473 -0
- disdrodb/etc/products/L1/global.yaml +13 -0
- disdrodb/etc/products/L2E/10MIN.yaml +12 -0
- disdrodb/etc/products/L2E/1MIN.yaml +1 -0
- disdrodb/etc/products/L2E/global.yaml +22 -0
- disdrodb/etc/products/L2M/10MIN.yaml +12 -0
- disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
- disdrodb/etc/products/L2M/global.yaml +26 -0
- disdrodb/issue/writer.py +2 -0
- disdrodb/l0/__init__.py +13 -0
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
- disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
- disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
- disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
- disdrodb/l0/l0a_processing.py +37 -32
- disdrodb/l0/l0b_nc_processing.py +118 -8
- disdrodb/l0/l0b_processing.py +30 -65
- disdrodb/l0/l0c_processing.py +369 -259
- disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +7 -0
- disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
- disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
- disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
- disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
- disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/ARM/ARM_PARSIVEL2.py +4 -0
- disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
- disdrodb/l0/readers/PARSIVEL2/CANADA/UQAM_NC.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
- disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
- disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
- disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → MPI/BCO_PARSIVEL2.py} +41 -71
- disdrodb/l0/readers/PARSIVEL2/MPI/BOWTIE.py +220 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
- disdrodb/l0/readers/PARSIVEL2/NASA/LPVEX.py +109 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +5 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
- disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
- disdrodb/l0/readers/PARSIVEL2/USA/C3WE.py +146 -0
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
- disdrodb/l1/__init__.py +5 -0
- disdrodb/l1/fall_velocity.py +46 -0
- disdrodb/l1/filters.py +34 -20
- disdrodb/l1/processing.py +46 -45
- disdrodb/l1/resampling.py +77 -66
- disdrodb/l1_env/routines.py +18 -3
- disdrodb/l2/__init__.py +7 -0
- disdrodb/l2/empirical_dsd.py +58 -10
- disdrodb/l2/processing.py +268 -117
- disdrodb/metadata/checks.py +132 -125
- disdrodb/metadata/standards.py +3 -1
- disdrodb/psd/fitting.py +631 -345
- disdrodb/psd/models.py +9 -6
- disdrodb/routines/__init__.py +54 -0
- disdrodb/{l0/routines.py → routines/l0.py} +316 -355
- disdrodb/{l1/routines.py → routines/l1.py} +76 -116
- disdrodb/routines/l2.py +1019 -0
- disdrodb/{routines.py → routines/wrappers.py} +98 -10
- disdrodb/scattering/__init__.py +16 -4
- disdrodb/scattering/axis_ratio.py +61 -37
- disdrodb/scattering/permittivity.py +504 -0
- disdrodb/scattering/routines.py +746 -184
- disdrodb/summary/__init__.py +17 -0
- disdrodb/summary/routines.py +4196 -0
- disdrodb/utils/archiving.py +434 -0
- disdrodb/utils/attrs.py +68 -125
- disdrodb/utils/cli.py +5 -5
- disdrodb/utils/compression.py +30 -1
- disdrodb/utils/dask.py +121 -9
- disdrodb/utils/dataframe.py +61 -7
- disdrodb/utils/decorators.py +31 -0
- disdrodb/utils/directories.py +35 -15
- disdrodb/utils/encoding.py +37 -19
- disdrodb/{l2 → utils}/event.py +15 -173
- disdrodb/utils/logger.py +14 -7
- disdrodb/utils/manipulations.py +81 -0
- disdrodb/utils/routines.py +166 -0
- disdrodb/utils/subsetting.py +214 -0
- disdrodb/utils/time.py +35 -177
- disdrodb/utils/writer.py +20 -7
- disdrodb/utils/xarray.py +5 -4
- disdrodb/viz/__init__.py +13 -0
- disdrodb/viz/plots.py +398 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/METADATA +4 -3
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/RECORD +139 -98
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/entry_points.txt +2 -0
- disdrodb/l1/encoding_attrs.py +0 -642
- disdrodb/l2/processing_options.py +0 -213
- disdrodb/l2/routines.py +0 -868
- /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/WHEEL +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.2.dist-info → disdrodb-0.1.4.dist-info}/top_level.txt +0 -0
disdrodb/scattering/routines.py
CHANGED
|
@@ -17,32 +17,55 @@
|
|
|
17
17
|
"""Implement PSD scattering routines."""
|
|
18
18
|
|
|
19
19
|
import itertools
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
20
22
|
|
|
21
23
|
import dask
|
|
22
24
|
import numpy as np
|
|
23
25
|
import xarray as xr
|
|
24
|
-
from pytmatrix import orientation, radar, refractive, tmatrix_aux
|
|
25
|
-
from pytmatrix.psd import BinnedPSD, PSDIntegrator
|
|
26
|
-
from pytmatrix.tmatrix import Scatterer
|
|
27
26
|
|
|
28
|
-
from disdrodb.
|
|
29
|
-
from disdrodb.
|
|
27
|
+
from disdrodb.configs import get_scattering_table_dir
|
|
28
|
+
from disdrodb.constants import DIAMETER_DIMENSION
|
|
29
|
+
from disdrodb.l1.filters import filter_diameter_bins
|
|
30
|
+
from disdrodb.psd.models import BinnedPSD, create_psd, get_required_parameters
|
|
31
|
+
from disdrodb.scattering.axis_ratio import check_axis_ratio_model, get_axis_ratio_model
|
|
32
|
+
from disdrodb.scattering.permittivity import (
|
|
33
|
+
check_permittivity_model,
|
|
34
|
+
get_rayleigh_dielectric_factor,
|
|
35
|
+
get_refractive_index,
|
|
36
|
+
)
|
|
37
|
+
from disdrodb.utils.logger import log_info
|
|
38
|
+
from disdrodb.utils.manipulations import get_diameter_bin_edges
|
|
30
39
|
from disdrodb.utils.warnings import suppress_warnings
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
RADAR_OPTIONS = [
|
|
44
|
+
"frequency",
|
|
45
|
+
"diameter_max",
|
|
46
|
+
"num_points",
|
|
47
|
+
"canting_angle_std",
|
|
48
|
+
"axis_ratio_model",
|
|
49
|
+
"permittivity_model",
|
|
50
|
+
"water_temperature",
|
|
51
|
+
"elevation_angle",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Common radar frequencies (in GHz)
|
|
55
|
+
frequency_dict = {
|
|
56
|
+
"S": 2.70, # e.g. NEXRAD radars
|
|
57
|
+
"C": 5.4, # e.g. MeteoSwiss Rad4Alp radars
|
|
58
|
+
"X": 9.4, # e.g. LTE MXPOL radar
|
|
59
|
+
"Ku": 13.6, # e.g. DPR-Ku
|
|
60
|
+
"K": 24.2, # e.g. MRR-PRO
|
|
61
|
+
"Ka": 35.5, # e.g. DPR-Ka
|
|
62
|
+
"W": 94.05, # e.g. CloudSat, EarthCare
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
|
|
43
66
|
def available_radar_bands():
|
|
44
67
|
"""Return a list of the available radar bands."""
|
|
45
|
-
return list(
|
|
68
|
+
return list(frequency_dict)
|
|
46
69
|
|
|
47
70
|
|
|
48
71
|
def check_radar_band(radar_band):
|
|
@@ -53,95 +76,465 @@ def check_radar_band(radar_band):
|
|
|
53
76
|
return radar_band
|
|
54
77
|
|
|
55
78
|
|
|
56
|
-
def
|
|
57
|
-
"""
|
|
58
|
-
|
|
79
|
+
def _check_frequency(frequency):
|
|
80
|
+
"""Check the validity of the specified frequency."""
|
|
81
|
+
if isinstance(frequency, str):
|
|
82
|
+
frequency = check_radar_band(frequency)
|
|
83
|
+
frequency = frequency_dict[frequency]
|
|
84
|
+
return frequency
|
|
85
|
+
if not isinstance(frequency, (int, float)):
|
|
86
|
+
raise TypeError(f"Frequency {frequency} must be a string or a number.")
|
|
87
|
+
return frequency
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def ensure_numerical_frequency(frequency):
|
|
91
|
+
"""Ensure that the frequencies are numerical values in GHz."""
|
|
92
|
+
if isinstance(frequency, (str, int, float)):
|
|
93
|
+
frequency = [frequency]
|
|
94
|
+
frequency = np.array([_check_frequency(f) for f in frequency])
|
|
95
|
+
return frequency.squeeze()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Wavelength, Frequency Conversion
|
|
99
|
+
def wavelength_to_frequency(wavelength):
|
|
100
|
+
"""Convert wavelength in millimeters to frequency in GHz."""
|
|
101
|
+
c = 299_792_458 # speed of light in m/s
|
|
102
|
+
frequency = c / np.array(wavelength) / 1e6
|
|
103
|
+
return frequency
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def frequency_to_wavelength(frequency):
|
|
107
|
+
"""Convert frequency in GHz to wavelength millimeters."""
|
|
108
|
+
c = 299_792_458 # speed of light in m/s
|
|
109
|
+
wavelength = c / np.array(frequency) / 1e6
|
|
59
110
|
return wavelength
|
|
60
111
|
|
|
61
112
|
|
|
62
|
-
def
|
|
63
|
-
"""
|
|
113
|
+
def get_backward_geometry(elevation_angle):
|
|
114
|
+
"""Define backward geometry given a radar elevation angle."""
|
|
115
|
+
# - Format (thet0, thet0, phi0, phi0, alpha, beta
|
|
116
|
+
# - thet0, thet0, thet: The zenith angles of incident and scattered radiation (default to 90)
|
|
117
|
+
# - phi0, phi: The azimuth angles of incident and scattered radiation (default to 0 and 180)
|
|
118
|
+
# - alpha, beta: Defaults to 0.0, 0.0. Valid values: alpha = [0, 360] beta = [0, 180]
|
|
119
|
+
|
|
120
|
+
# Retrieve zenith angle of incident beam (from vertical)
|
|
121
|
+
theta = 90.0 - elevation_angle
|
|
122
|
+
|
|
123
|
+
# Return (thet0, thet0, phi0, phi0, alpha, beta) tuple
|
|
124
|
+
return (theta, 180 - theta, 0.0, 180, 0.0, 0.0)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_forward_geometry(elevation_angle):
|
|
128
|
+
"""Define forward geometry given a radar elevation angle."""
|
|
129
|
+
# - Format (thet0, thet0, phi0, phi0, alpha, beta
|
|
130
|
+
# - thet0, thet0, thet: The zenith angles of incident and scattered radiation (default to 90)
|
|
131
|
+
# - phi0, phi: The azimuth angles of incident and scattered radiation (default to 0 and 180)
|
|
132
|
+
# - alpha, beta: Defaults to 0.0, 0.0. Valid values: alpha = [0, 360] beta = [0, 180]
|
|
133
|
+
|
|
134
|
+
# Retrieve zenith angle of incident beam (from vertical)
|
|
135
|
+
theta = 90.0 - elevation_angle
|
|
136
|
+
|
|
137
|
+
# Return (thet0, thet0, phi0, phi0, alpha, beta) tuple
|
|
138
|
+
return (theta, theta, 0.0, 0.0, 0.0, 0.0)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# from pytmatrix import tmatrix_aux
|
|
142
|
+
# get_backward_geometry(0)
|
|
143
|
+
# tmatrix_aux.geom_horiz_back
|
|
144
|
+
# get_backward_geometry(90)
|
|
145
|
+
# tmatrix_aux.geom_vert_back # phi0 varies (180 instead of pytmatrix 0)
|
|
146
|
+
|
|
147
|
+
# get_forward_geometry(0)
|
|
148
|
+
# tmatrix_aux.geom_horiz_forw
|
|
149
|
+
# get_forward_geometry(90) # theta and thet0 are 0 instead of 180
|
|
150
|
+
# tmatrix_aux.geom_vert_forw
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def initialize_scatterer(
|
|
154
|
+
wavelength,
|
|
155
|
+
refractive_index,
|
|
156
|
+
num_points=1024,
|
|
157
|
+
diameter_max=8,
|
|
158
|
+
canting_angle_std=7,
|
|
159
|
+
axis_ratio_model="Thurai2007",
|
|
160
|
+
elevation_angle=0,
|
|
161
|
+
):
|
|
162
|
+
"""Initialize T-matrix scatterer object for a given frequency.
|
|
163
|
+
|
|
164
|
+
Load a scatterer object with cached scattering table.
|
|
165
|
+
|
|
166
|
+
If the scattering table does not exist at the specified location, it will be
|
|
167
|
+
created and saved to disk. If the file is found, it will be loaded and used
|
|
168
|
+
to configure the scatterer.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
wavelength : float
|
|
173
|
+
Radar wavelength in mm.
|
|
174
|
+
refractive_index: complex
|
|
175
|
+
Water refractive index.
|
|
176
|
+
num_points: int
|
|
177
|
+
Number of bins into which discretize the PSD.
|
|
178
|
+
diameter_max : float
|
|
179
|
+
Maximum drop diameter in millimeters for the scattering table.
|
|
180
|
+
canting_angle_std : float, optional
|
|
181
|
+
Standard deviation of the canting angle distribution in degrees,
|
|
182
|
+
by default 7.
|
|
183
|
+
axis_ratio_model: str
|
|
184
|
+
Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
|
|
185
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
186
|
+
elevation_angle: str
|
|
187
|
+
Radar elevation angle in degrees.
|
|
188
|
+
Specify 90 degrees for vertically pointing radars.
|
|
189
|
+
The default is 0 degrees.
|
|
190
|
+
scattering_table_dir : str or Path, optional
|
|
191
|
+
Directory path where T-Matrix scattering tables are stored. If None, the default
|
|
192
|
+
location will be used.
|
|
193
|
+
verbose: bool
|
|
194
|
+
Whether to verbose the computation of the scattering table. The default is False.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
scatterer : Scatterer
|
|
199
|
+
A scatterer object with the PSD integrator configured and scattering
|
|
200
|
+
table loaded or generated.
|
|
201
|
+
"""
|
|
202
|
+
from pytmatrix import orientation
|
|
203
|
+
from pytmatrix.psd import PSDIntegrator
|
|
204
|
+
from pytmatrix.tmatrix import Scatterer
|
|
205
|
+
|
|
64
206
|
# Retrieve custom axis ratio function
|
|
65
|
-
axis_ratio_func =
|
|
207
|
+
axis_ratio_func = get_axis_ratio_model(axis_ratio_model)
|
|
208
|
+
|
|
209
|
+
# Define radar dielectric factor
|
|
210
|
+
Kw_sqr = get_rayleigh_dielectric_factor(refractive_index)
|
|
66
211
|
|
|
67
|
-
#
|
|
68
|
-
# -
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
212
|
+
# Define backward and forward geometries
|
|
213
|
+
# - Format (thet0, thet0, phi0, phi0, alpha, beta
|
|
214
|
+
backward_geom = get_backward_geometry(elevation_angle)
|
|
215
|
+
forward_geom = get_forward_geometry(elevation_angle)
|
|
216
|
+
|
|
217
|
+
# ---------------------------------------------------------------.
|
|
218
|
+
# For W band limits diameter_max up to 9.5, otherwise the kernel dies !
|
|
219
|
+
if wavelength < 3.5:
|
|
220
|
+
diameter_max = min(diameter_max, 9.5)
|
|
72
221
|
|
|
73
222
|
# ---------------------------------------------------------------.
|
|
74
223
|
# Initialize Scatterer class
|
|
75
|
-
|
|
224
|
+
# - By specifying m, we assume same refractive index for all particles diameters
|
|
225
|
+
scatterer = Scatterer(wavelength=wavelength, m=refractive_index, Kw_sqr=Kw_sqr)
|
|
226
|
+
|
|
227
|
+
# - Define geometry
|
|
228
|
+
scatterer.set_geometry(backward_geom)
|
|
229
|
+
|
|
230
|
+
# - Define orientation methods
|
|
231
|
+
# --> Alternatives: orient_averaged_adaptive, orient_single,
|
|
232
|
+
# --> Speed: orient_single > orient_averaged_fixed > orient_averaged_adaptive
|
|
233
|
+
scatterer.orient = orientation.orient_averaged_fixed
|
|
234
|
+
|
|
76
235
|
# - Define particle orientation PDF for orientational averaging
|
|
77
236
|
# --> The standard deviation of the angle with respect to vertical orientation (the canting angle).
|
|
78
237
|
scatterer.or_pdf = orientation.gaussian_pdf(std=canting_angle_std)
|
|
79
|
-
# - Define orientation methods
|
|
80
|
-
# --> Alternatives: orient_averaged_fixed, orient_single
|
|
81
|
-
scatterer.orient = orientation.orient_averaged_fixed
|
|
82
238
|
|
|
83
239
|
# ---------------------------------------------------------------.
|
|
84
240
|
# Initialize PSDIntegrator
|
|
85
241
|
scatterer.psd_integrator = PSDIntegrator()
|
|
86
242
|
# - Define axis_ratio_func
|
|
87
243
|
# --> The Scatterer class expects horizontal to vertical
|
|
244
|
+
# --> Axis ratio model are defined to return vertical to horizontal aspect ratio !
|
|
88
245
|
scatterer.psd_integrator.axis_ratio_func = lambda D: 1.0 / axis_ratio_func(D)
|
|
89
246
|
# - Define function to compute refrative index (as function of D)
|
|
90
247
|
# scatterer.psd_integrator.m_func = None # Use constant value of scatterer.m
|
|
91
248
|
# - Define number of points over which to integrate
|
|
92
|
-
scatterer.psd_integrator.num_points =
|
|
249
|
+
scatterer.psd_integrator.num_points = num_points
|
|
93
250
|
# - Define maximum drop diameter
|
|
94
|
-
scatterer.psd_integrator.D_max =
|
|
251
|
+
scatterer.psd_integrator.D_max = diameter_max
|
|
95
252
|
# - Define geometries
|
|
96
|
-
|
|
253
|
+
# --> convention: first is backward, second is forward
|
|
254
|
+
scatterer.psd_integrator.geometries = (backward_geom, forward_geom)
|
|
255
|
+
return scatterer
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def calculate_scatterer(
|
|
259
|
+
wavelength,
|
|
260
|
+
refractive_index,
|
|
261
|
+
num_points=1024,
|
|
262
|
+
diameter_max=8,
|
|
263
|
+
canting_angle_std=7,
|
|
264
|
+
axis_ratio_model="Thurai2007",
|
|
265
|
+
elevation_angle=0,
|
|
266
|
+
):
|
|
267
|
+
"""Initialize T-matrix scatterer object for a given frequency.
|
|
268
|
+
|
|
269
|
+
Load a scatterer object with cached scattering table.
|
|
270
|
+
|
|
271
|
+
If the scattering table does not exist at the specified location, it will be
|
|
272
|
+
created and saved to disk. If the file is found, it will be loaded and used
|
|
273
|
+
to configure the scatterer.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
wavelength : float
|
|
278
|
+
Radar wavelength in millimeters.
|
|
279
|
+
num_points: int
|
|
280
|
+
Number of bins into which discretize the PSD.
|
|
281
|
+
diameter_max : float
|
|
282
|
+
Maximum drop diameter in millimeters for the scattering table.
|
|
283
|
+
canting_angle_std : float, optional
|
|
284
|
+
Standard deviation of the canting angle distribution in degrees,
|
|
285
|
+
by default 7.
|
|
286
|
+
axis_ratio_model : str, optional
|
|
287
|
+
Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
|
|
288
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
289
|
+
elevation_angle: str
|
|
290
|
+
Radar elevation angle in degrees.
|
|
291
|
+
Specify 90 degrees for vertically pointing radars.
|
|
292
|
+
The default is 0 degrees.
|
|
293
|
+
|
|
294
|
+
Returns
|
|
295
|
+
-------
|
|
296
|
+
scatterer : Scatterer
|
|
297
|
+
A scatterer object with the PSD integrator configured and scattering
|
|
298
|
+
table loaded or generated.
|
|
299
|
+
"""
|
|
97
300
|
# ---------------------------------------------------------------.
|
|
98
|
-
# Initialize
|
|
301
|
+
# Initialize Scatterer class
|
|
302
|
+
scatterer = initialize_scatterer(
|
|
303
|
+
wavelength=wavelength,
|
|
304
|
+
refractive_index=refractive_index,
|
|
305
|
+
num_points=num_points,
|
|
306
|
+
diameter_max=diameter_max,
|
|
307
|
+
canting_angle_std=canting_angle_std,
|
|
308
|
+
axis_ratio_model=axis_ratio_model,
|
|
309
|
+
elevation_angle=elevation_angle,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# ---------------------------------------------------------------.
|
|
313
|
+
# Calculate scattering table
|
|
99
314
|
scatterer.psd_integrator.init_scatter_table(scatterer)
|
|
100
315
|
return scatterer
|
|
101
316
|
|
|
102
317
|
|
|
318
|
+
def load_scatterer(
|
|
319
|
+
frequency,
|
|
320
|
+
num_points=1024,
|
|
321
|
+
diameter_max=8,
|
|
322
|
+
canting_angle_std=7,
|
|
323
|
+
axis_ratio_model="Thurai2007",
|
|
324
|
+
permittivity_model="Turner2016",
|
|
325
|
+
water_temperature=10,
|
|
326
|
+
elevation_angle=0,
|
|
327
|
+
scattering_table_dir=None,
|
|
328
|
+
verbose=False,
|
|
329
|
+
):
|
|
330
|
+
"""
|
|
331
|
+
Load a scatterer object with cached scattering table.
|
|
332
|
+
|
|
333
|
+
If the scattering table does not exist at the specified location, it will be
|
|
334
|
+
created and saved to disk. If the file is found, it will be loaded and used
|
|
335
|
+
to configure the scatterer.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
frequency : float
|
|
340
|
+
Radar frequency in GHz.
|
|
341
|
+
num_points: int
|
|
342
|
+
Number of bins into which discretize the PSD.
|
|
343
|
+
diameter_max : float
|
|
344
|
+
Maximum drop diameter in millimeters for the scattering table.
|
|
345
|
+
canting_angle_std : float, optional
|
|
346
|
+
Standard deviation of the canting angle distribution in degrees,
|
|
347
|
+
by default 7.
|
|
348
|
+
axis_ratio_model : str, optional
|
|
349
|
+
Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
|
|
350
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
351
|
+
permittivity_model : str
|
|
352
|
+
Permittivity model to use to compute the refractive index and the
|
|
353
|
+
rayleigh_dielectric_factor. The default is ``Turner2016``.
|
|
354
|
+
See available models with ``disdrodb.scattering.available_permittivity_models()``.
|
|
355
|
+
water_temperature : float
|
|
356
|
+
Water temperature in degree Celsius to be used in the permittivity model.
|
|
357
|
+
The default is 10 degC.
|
|
358
|
+
elevation_angle: str
|
|
359
|
+
Radar elevation angle in degrees.
|
|
360
|
+
Specify 90 degrees for vertically pointing radars.
|
|
361
|
+
The default is 0 degrees.
|
|
362
|
+
scattering_table_dir : str or Path, optional
|
|
363
|
+
Directory path where T-Matrix scattering tables are stored. If None, the default
|
|
364
|
+
location will be used.
|
|
365
|
+
verbose: bool
|
|
366
|
+
Whether to verbose the computation of the scattering table. The default is False.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
scatterer : Scatterer
|
|
371
|
+
A scatterer object with the PSD integrator configured and scattering
|
|
372
|
+
table loaded or generated.
|
|
373
|
+
"""
|
|
374
|
+
# Define wavelength (in mm)
|
|
375
|
+
wavelength = frequency_to_wavelength(frequency)
|
|
376
|
+
|
|
377
|
+
# Define complex refractive index
|
|
378
|
+
refractive_index = get_refractive_index(
|
|
379
|
+
frequency=frequency,
|
|
380
|
+
temperature=water_temperature,
|
|
381
|
+
permittivity_model=permittivity_model,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Retrieve scattering table directory
|
|
385
|
+
scattering_table_dir = get_scattering_table_dir(scattering_table_dir)
|
|
386
|
+
|
|
387
|
+
# Define a filename based on the key parameters
|
|
388
|
+
filename = "_".join(
|
|
389
|
+
[
|
|
390
|
+
"ScatteringTable",
|
|
391
|
+
f"wl-{wavelength:.2f}",
|
|
392
|
+
f"el-{elevation_angle:.1f}",
|
|
393
|
+
f"dmax-{diameter_max:.1f}",
|
|
394
|
+
f"npts-{num_points}",
|
|
395
|
+
f"m-{refractive_index:.3f}",
|
|
396
|
+
f"cant-{canting_angle_std:.1f}",
|
|
397
|
+
f"ar-{axis_ratio_model}.pkl",
|
|
398
|
+
],
|
|
399
|
+
)
|
|
400
|
+
scatter_table_filepath = os.path.join(scattering_table_dir, filename)
|
|
401
|
+
|
|
402
|
+
# Load or create scattering table
|
|
403
|
+
if os.path.exists(scatter_table_filepath):
|
|
404
|
+
scatterer = initialize_scatterer(
|
|
405
|
+
wavelength=wavelength,
|
|
406
|
+
refractive_index=refractive_index,
|
|
407
|
+
num_points=num_points,
|
|
408
|
+
diameter_max=diameter_max,
|
|
409
|
+
canting_angle_std=canting_angle_std,
|
|
410
|
+
axis_ratio_model=axis_ratio_model,
|
|
411
|
+
elevation_angle=elevation_angle,
|
|
412
|
+
)
|
|
413
|
+
_ = scatterer.psd_integrator.load_scatter_table(scatter_table_filepath)
|
|
414
|
+
|
|
415
|
+
else:
|
|
416
|
+
if verbose:
|
|
417
|
+
msg = f"- Computing pyTmatrix {filename}"
|
|
418
|
+
log_info(logger=logger, msg=msg, verbose=verbose)
|
|
419
|
+
|
|
420
|
+
# Calculate scatterer
|
|
421
|
+
scatterer = calculate_scatterer(
|
|
422
|
+
wavelength=wavelength,
|
|
423
|
+
refractive_index=refractive_index,
|
|
424
|
+
num_points=num_points,
|
|
425
|
+
diameter_max=diameter_max,
|
|
426
|
+
canting_angle_std=canting_angle_std,
|
|
427
|
+
axis_ratio_model=axis_ratio_model,
|
|
428
|
+
elevation_angle=elevation_angle,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
scatterer.psd_integrator.save_scatter_table(scatter_table_filepath)
|
|
432
|
+
return scatterer
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def precompute_scattering_tables(
|
|
436
|
+
frequency,
|
|
437
|
+
num_points,
|
|
438
|
+
diameter_max,
|
|
439
|
+
canting_angle_std,
|
|
440
|
+
axis_ratio_model,
|
|
441
|
+
permittivity_model,
|
|
442
|
+
water_temperature,
|
|
443
|
+
elevation_angle,
|
|
444
|
+
verbose=True,
|
|
445
|
+
):
|
|
446
|
+
"""Precompute the pyTMatrix scattering tables required for radar variables simulations."""
|
|
447
|
+
from disdrodb.scattering.routines import get_list_simulations_params, load_scatterer
|
|
448
|
+
|
|
449
|
+
# Define parameters for all requested simulations
|
|
450
|
+
list_params = get_list_simulations_params(
|
|
451
|
+
frequency=frequency,
|
|
452
|
+
num_points=num_points,
|
|
453
|
+
diameter_max=diameter_max,
|
|
454
|
+
canting_angle_std=canting_angle_std,
|
|
455
|
+
axis_ratio_model=axis_ratio_model,
|
|
456
|
+
permittivity_model=permittivity_model,
|
|
457
|
+
water_temperature=water_temperature,
|
|
458
|
+
elevation_angle=elevation_angle,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Compute require scattering tables
|
|
462
|
+
for params in list_params:
|
|
463
|
+
# Initialize scattering table
|
|
464
|
+
_ = load_scatterer(
|
|
465
|
+
verbose=verbose,
|
|
466
|
+
**params,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
####----------------------------------------------------------------------
|
|
471
|
+
#### Scattering functions
|
|
472
|
+
|
|
473
|
+
|
|
103
474
|
def compute_radar_variables(scatterer):
|
|
104
475
|
"""Compute radar variables for a given scatter object with a specified PSD.
|
|
105
476
|
|
|
106
477
|
To speed up computations, this function should input a scatterer object with
|
|
107
478
|
a preinitialized scattering table.
|
|
108
479
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
480
|
+
from pytmatrix import radar
|
|
481
|
+
|
|
482
|
+
with suppress_warnings():
|
|
483
|
+
radar_vars = {}
|
|
484
|
+
# Retrieve backward and forward_geometries
|
|
485
|
+
# - Convention (first is backward, second is forward)
|
|
486
|
+
backward_geom = scatterer.psd_integrator.geometries[0]
|
|
487
|
+
forward_geom = scatterer.psd_integrator.geometries[1]
|
|
488
|
+
|
|
489
|
+
# Set backward scattering for reflectivity calculations
|
|
490
|
+
scatterer.set_geometry(backward_geom)
|
|
491
|
+
|
|
492
|
+
radar_vars["DBZH"] = 10 * np.log10(radar.refl(scatterer, h_pol=True)) # dBZ
|
|
493
|
+
radar_vars["DBZV"] = 10 * np.log10(radar.refl(scatterer, h_pol=False)) # dBZ
|
|
494
|
+
|
|
495
|
+
radar_vars["ZDR"] = 10 * np.log10(radar.Zdr(scatterer)) # dB
|
|
496
|
+
radar_vars["ZDR"] = np.where(np.isfinite(radar_vars["ZDR"]), radar_vars["ZDR"], np.nan)
|
|
497
|
+
|
|
498
|
+
radar_vars["LDR"] = 10 * np.log10(radar.ldr(scatterer)) # dBZ
|
|
499
|
+
radar_vars["LDR"] = np.where(np.isfinite(radar_vars["LDR"]), radar_vars["LDR"], np.nan)
|
|
500
|
+
|
|
501
|
+
radar_vars["RHOHV"] = radar.rho_hv(scatterer) # deg/km
|
|
502
|
+
radar_vars["DELTAHV"] = radar.delta_hv(scatterer) * 180.0 / np.pi # [deg]
|
|
503
|
+
|
|
504
|
+
# Set forward scattering for attenuation and phase calculations
|
|
505
|
+
scatterer.set_geometry(forward_geom)
|
|
506
|
+
radar_vars["KDP"] = radar.Kdp(scatterer) # deg/km
|
|
507
|
+
radar_vars["AH"] = radar.Ai(scatterer, h_pol=True) # dB/km
|
|
508
|
+
radar_vars["AV"] = radar.Ai(scatterer, h_pol=False) # dB/km
|
|
509
|
+
radar_vars["ADP"] = radar_vars["AH"] - radar_vars["AV"] # dB/km
|
|
119
510
|
return radar_vars
|
|
120
511
|
|
|
121
512
|
|
|
513
|
+
# Radar variables computed by DISDRODB
|
|
514
|
+
# - Must reflect dictionary order output of compute_radar_variables
|
|
515
|
+
RADAR_VARIABLES = ["DBZH", "DBZV", "ZDR", "LDR", "RHOHV", "DELTAHV", "KDP", "AH", "AV", "ADP"]
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _try_compute_radar_variables(scatterer):
|
|
519
|
+
with suppress_warnings():
|
|
520
|
+
try:
|
|
521
|
+
radar_vars = compute_radar_variables(scatterer)
|
|
522
|
+
output = np.array(list(radar_vars.values()))
|
|
523
|
+
except Exception:
|
|
524
|
+
output = np.zeros(len(RADAR_VARIABLES)) * np.nan
|
|
525
|
+
return output
|
|
526
|
+
|
|
527
|
+
|
|
122
528
|
def _estimate_empirical_radar_parameters(
|
|
123
529
|
drop_number_concentration,
|
|
124
530
|
bin_edges,
|
|
125
531
|
scatterer,
|
|
126
|
-
output_dictionary,
|
|
127
532
|
):
|
|
128
|
-
# Initialize bad results
|
|
129
|
-
if output_dictionary:
|
|
130
|
-
null_output = {"Zh": np.nan, "Zdr": np.nan, "rho_hv": np.nan, "ldr": np.nan, "Kdp": np.nan, "Ai": np.nan}
|
|
131
|
-
else:
|
|
132
|
-
null_output = np.array([np.nan, np.nan, np.nan, np.nan, np.nan, np.nan])
|
|
133
|
-
|
|
134
533
|
# Assign PSD model to the scatterer object
|
|
135
534
|
scatterer.psd = BinnedPSD(bin_edges, drop_number_concentration)
|
|
136
535
|
|
|
137
536
|
# Get radar variables
|
|
138
|
-
|
|
139
|
-
try:
|
|
140
|
-
radar_vars = compute_radar_variables(scatterer)
|
|
141
|
-
output = radar_vars if output_dictionary else np.array(list(radar_vars.values()))
|
|
142
|
-
except Exception:
|
|
143
|
-
output = null_output
|
|
144
|
-
return output
|
|
537
|
+
return _try_compute_radar_variables(scatterer)
|
|
145
538
|
|
|
146
539
|
|
|
147
540
|
def _estimate_model_radar_parameters(
|
|
@@ -149,30 +542,16 @@ def _estimate_model_radar_parameters(
|
|
|
149
542
|
psd_model,
|
|
150
543
|
psd_parameters_names,
|
|
151
544
|
scatterer,
|
|
152
|
-
output_dictionary,
|
|
153
545
|
):
|
|
154
|
-
# Initialize bad results
|
|
155
|
-
if output_dictionary:
|
|
156
|
-
null_output = {"Zh": np.nan, "Zdr": np.nan, "rho_hv": np.nan, "ldr": np.nan, "Kdp": np.nan, "Ai": np.nan}
|
|
157
|
-
else:
|
|
158
|
-
null_output = np.array([np.nan, np.nan, np.nan, np.nan, np.nan, np.nan])
|
|
159
|
-
|
|
160
546
|
# Assign PSD model to the scatterer object
|
|
161
547
|
parameters = dict(zip(psd_parameters_names, parameters))
|
|
162
548
|
scatterer.psd = create_psd(psd_model, parameters)
|
|
163
549
|
|
|
164
550
|
# Get radar variables
|
|
165
|
-
|
|
166
|
-
radar_vars = compute_radar_variables(scatterer)
|
|
167
|
-
try:
|
|
168
|
-
radar_vars = compute_radar_variables(scatterer)
|
|
169
|
-
output = radar_vars if output_dictionary else np.array(list(radar_vars.values()))
|
|
170
|
-
except Exception:
|
|
171
|
-
output = null_output
|
|
172
|
-
return output
|
|
551
|
+
return _try_compute_radar_variables(scatterer)
|
|
173
552
|
|
|
174
553
|
|
|
175
|
-
def
|
|
554
|
+
def select_psd_parameters(ds):
|
|
176
555
|
"""Return a xr.Dataset with only the PSD parameters as variable."""
|
|
177
556
|
psd_model = ds.attrs["disdrodb_psd_model"]
|
|
178
557
|
required_parameters = get_required_parameters(psd_model)
|
|
@@ -184,26 +563,44 @@ def get_psd_parameters(ds):
|
|
|
184
563
|
|
|
185
564
|
def get_model_radar_parameters(
|
|
186
565
|
ds,
|
|
187
|
-
|
|
188
|
-
|
|
566
|
+
frequency,
|
|
567
|
+
num_points=1024,
|
|
189
568
|
diameter_max=10,
|
|
190
|
-
|
|
569
|
+
canting_angle_std=7,
|
|
570
|
+
axis_ratio_model="Thurai2007",
|
|
571
|
+
permittivity_model="Turner2016",
|
|
572
|
+
water_temperature=10,
|
|
573
|
+
elevation_angle=0,
|
|
191
574
|
):
|
|
192
575
|
"""Compute radar parameters from a PSD model.
|
|
193
576
|
|
|
577
|
+
This function retrieve values for a single set of parameter only !
|
|
578
|
+
|
|
194
579
|
Parameters
|
|
195
580
|
----------
|
|
196
581
|
ds : xarray.Dataset
|
|
197
582
|
Dataset containing the parameters of the PSD model.
|
|
198
583
|
The dataset attribute disdrodb_psd_model specifies the PSD model to use.
|
|
199
|
-
|
|
200
|
-
|
|
584
|
+
frequency : float
|
|
585
|
+
Frequency in GHz for which to compute the radar parameters.
|
|
201
586
|
canting_angle_std : float, optional
|
|
202
|
-
Standard deviation of the canting angle. The default value is
|
|
587
|
+
Standard deviation of the canting angle. The default value is 10.
|
|
203
588
|
diameter_max : float, optional
|
|
204
589
|
Maximum diameter. The default value is 8 mm.
|
|
205
|
-
|
|
206
|
-
|
|
590
|
+
axis_ratio_model : str, optional
|
|
591
|
+
Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
|
|
592
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
593
|
+
permittivity_model : str
|
|
594
|
+
Permittivity model to use to compute the refractive index and the
|
|
595
|
+
rayleigh_dielectric_factor. The default is ``Turner2016``.
|
|
596
|
+
See available models with ``disdrodb.scattering.available_permittivity_models()``.
|
|
597
|
+
water_temperature : float
|
|
598
|
+
Water temperature in degree Celsius to be used in the permittivity model.
|
|
599
|
+
The default is 10 degC.
|
|
600
|
+
elevation_angle: str
|
|
601
|
+
Radar elevation angle in degrees.
|
|
602
|
+
Specify 90 degrees for vertically pointing radars.
|
|
603
|
+
The default is 0 degrees.
|
|
207
604
|
|
|
208
605
|
Returns
|
|
209
606
|
-------
|
|
@@ -213,36 +610,35 @@ def get_model_radar_parameters(
|
|
|
213
610
|
# Retrieve psd model and parameters.
|
|
214
611
|
psd_model = ds.attrs["disdrodb_psd_model"]
|
|
215
612
|
required_parameters = get_required_parameters(psd_model)
|
|
216
|
-
ds_parameters =
|
|
613
|
+
ds_parameters = select_psd_parameters(ds)
|
|
217
614
|
|
|
218
615
|
# Check argument validity
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
# Retrieve wavelengths in mm
|
|
223
|
-
wavelength = get_radar_wavelength(radar_band)
|
|
616
|
+
axis_ratio_model = check_axis_ratio_model(axis_ratio_model)
|
|
617
|
+
permittivity_model = check_permittivity_model(permittivity_model)
|
|
224
618
|
|
|
225
619
|
# Create DataArray with PSD parameters
|
|
226
|
-
da_parameters = ds_parameters.to_array(dim="psd_parameters")
|
|
620
|
+
da_parameters = ds_parameters.to_array(dim="psd_parameters")
|
|
227
621
|
|
|
228
622
|
# Initialize scattering table
|
|
229
|
-
scatterer =
|
|
230
|
-
|
|
623
|
+
scatterer = load_scatterer(
|
|
624
|
+
frequency=frequency,
|
|
625
|
+
num_points=num_points,
|
|
626
|
+
diameter_max=diameter_max,
|
|
231
627
|
canting_angle_std=canting_angle_std,
|
|
232
|
-
|
|
233
|
-
|
|
628
|
+
axis_ratio_model=axis_ratio_model,
|
|
629
|
+
permittivity_model=permittivity_model,
|
|
630
|
+
water_temperature=water_temperature,
|
|
631
|
+
elevation_angle=elevation_angle,
|
|
234
632
|
)
|
|
235
633
|
|
|
236
634
|
# Define kwargs
|
|
237
635
|
kwargs = {
|
|
238
|
-
"output_dictionary": False,
|
|
239
636
|
"psd_model": psd_model,
|
|
240
637
|
"psd_parameters_names": required_parameters,
|
|
241
638
|
"scatterer": scatterer,
|
|
242
639
|
}
|
|
243
640
|
|
|
244
641
|
# Loop over each PSD (not in parallel --> dask="forbidden")
|
|
245
|
-
# - It costs much more to initiate the scatterer rather than looping over timesteps !
|
|
246
642
|
da_radar = xr.apply_ufunc(
|
|
247
643
|
_estimate_model_radar_parameters,
|
|
248
644
|
da_parameters,
|
|
@@ -250,83 +646,110 @@ def get_model_radar_parameters(
|
|
|
250
646
|
input_core_dims=[["psd_parameters"]],
|
|
251
647
|
output_core_dims=[["radar_variables"]],
|
|
252
648
|
vectorize=True,
|
|
253
|
-
dask="
|
|
254
|
-
dask_gufunc_kwargs={
|
|
649
|
+
dask="parallelized",
|
|
650
|
+
dask_gufunc_kwargs={
|
|
651
|
+
"output_sizes": {"radar_variables": len(RADAR_VARIABLES)},
|
|
652
|
+
"allow_rechunk": True,
|
|
653
|
+
}, # lengths of the new output_core_dims dimensions.
|
|
255
654
|
output_dtypes=["float64"],
|
|
256
655
|
)
|
|
257
656
|
|
|
258
|
-
#
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
ds_radar = ds_radar.expand_dims(dim=dims_dict)
|
|
657
|
+
# Finalize radar dataset (add name, coordinates)
|
|
658
|
+
ds_radar = _finalize_radar_dataset(
|
|
659
|
+
da_radar=da_radar,
|
|
660
|
+
frequency=frequency,
|
|
661
|
+
num_points=num_points,
|
|
662
|
+
diameter_max=diameter_max,
|
|
663
|
+
canting_angle_std=canting_angle_std,
|
|
664
|
+
axis_ratio_model=axis_ratio_model,
|
|
665
|
+
permittivity_model=permittivity_model,
|
|
666
|
+
water_temperature=water_temperature,
|
|
667
|
+
elevation_angle=elevation_angle,
|
|
668
|
+
)
|
|
272
669
|
return ds_radar
|
|
273
670
|
|
|
274
671
|
|
|
275
672
|
def get_empirical_radar_parameters(
|
|
276
673
|
ds,
|
|
277
|
-
|
|
674
|
+
frequency,
|
|
675
|
+
num_points=1024,
|
|
676
|
+
diameter_max=10,
|
|
278
677
|
canting_angle_std=7,
|
|
279
|
-
|
|
280
|
-
|
|
678
|
+
axis_ratio_model="Thurai2007",
|
|
679
|
+
permittivity_model="Turner2016",
|
|
680
|
+
water_temperature=10,
|
|
681
|
+
elevation_angle=0,
|
|
281
682
|
):
|
|
282
|
-
"""Compute radar parameters from empirical drop number concentration.
|
|
683
|
+
"""Compute radar parameters from an empirical drop number concentration.
|
|
684
|
+
|
|
685
|
+
This function retrieve values for a single set of parameter only !
|
|
283
686
|
|
|
284
687
|
Parameters
|
|
285
688
|
----------
|
|
286
689
|
ds : xarray.Dataset
|
|
287
690
|
Dataset containing the drop number concentration variable.
|
|
288
|
-
|
|
289
|
-
|
|
691
|
+
frequency : float
|
|
692
|
+
Frequency in GHz for which to compute the radar parameters.
|
|
290
693
|
canting_angle_std : float, optional
|
|
291
|
-
Standard deviation of the canting angle. The default value is
|
|
694
|
+
Standard deviation of the canting angle. The default value is 10.
|
|
292
695
|
diameter_max : float, optional
|
|
293
696
|
Maximum diameter. The default value is 8 mm.
|
|
294
|
-
|
|
295
|
-
|
|
697
|
+
axis_ratio_model : str, optional
|
|
698
|
+
Axis ratio model used to shape hydrometeors. The default is ``"Thurai2007"``.
|
|
699
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
700
|
+
permittivity_model : str
|
|
701
|
+
Permittivity model to use to compute the refractive index and the
|
|
702
|
+
rayleigh_dielectric_factor. The default is ``Turner2016``.
|
|
703
|
+
See available models with ``disdrodb.scattering.available_permittivity_models()``.
|
|
704
|
+
water_temperature : float
|
|
705
|
+
Water temperature in degree Celsius to be used in the permittivity model.
|
|
706
|
+
The default is 10 degC.
|
|
707
|
+
elevation_angle: str
|
|
708
|
+
Radar elevation angle in degrees.
|
|
709
|
+
Specify 90 degrees for vertically pointing radars.
|
|
710
|
+
The default is 0 degrees.
|
|
296
711
|
|
|
297
712
|
Returns
|
|
298
713
|
-------
|
|
299
714
|
xarray.Dataset
|
|
300
715
|
Dataset containing the computed radar parameters.
|
|
301
716
|
"""
|
|
717
|
+
# Subset dataset based on diameter max
|
|
718
|
+
ds = filter_diameter_bins(ds=ds, maximum_diameter=diameter_max)
|
|
719
|
+
|
|
302
720
|
# Define inputs
|
|
303
|
-
da_drop_number_concentration = ds["drop_number_concentration"].compute()
|
|
721
|
+
da_drop_number_concentration = ds["drop_number_concentration"] # .compute()
|
|
722
|
+
|
|
723
|
+
# Set all zeros drop number concentration to np.nan
|
|
724
|
+
# --> Otherwise inf can appear in the output
|
|
725
|
+
# --> Note that if a single np.nan is present, the output simulation will be NaN values
|
|
726
|
+
valid_obs = da_drop_number_concentration.sum(dim=DIAMETER_DIMENSION) != 0
|
|
727
|
+
da_drop_number_concentration = da_drop_number_concentration.where(valid_obs)
|
|
304
728
|
|
|
305
729
|
# Define bin edges
|
|
306
|
-
bin_edges =
|
|
730
|
+
bin_edges = get_diameter_bin_edges(ds)
|
|
307
731
|
|
|
308
732
|
# Check argument validity
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
# Retrieve wavelengths in mm
|
|
313
|
-
wavelength = get_radar_wavelength(radar_band)
|
|
733
|
+
axis_ratio_model = check_axis_ratio_model(axis_ratio_model)
|
|
734
|
+
permittivity_model = check_permittivity_model(permittivity_model)
|
|
314
735
|
|
|
315
736
|
# Initialize scattering table
|
|
316
|
-
scatterer =
|
|
317
|
-
|
|
737
|
+
scatterer = load_scatterer(
|
|
738
|
+
frequency=frequency,
|
|
739
|
+
num_points=num_points,
|
|
740
|
+
diameter_max=diameter_max,
|
|
318
741
|
canting_angle_std=canting_angle_std,
|
|
319
|
-
|
|
320
|
-
|
|
742
|
+
axis_ratio_model=axis_ratio_model,
|
|
743
|
+
permittivity_model=permittivity_model,
|
|
744
|
+
water_temperature=water_temperature,
|
|
745
|
+
elevation_angle=elevation_angle,
|
|
321
746
|
)
|
|
322
747
|
|
|
323
748
|
# Define kwargs
|
|
324
749
|
kwargs = {
|
|
325
|
-
"output_dictionary": False,
|
|
326
750
|
"bin_edges": bin_edges,
|
|
327
751
|
"scatterer": scatterer,
|
|
328
752
|
}
|
|
329
|
-
|
|
330
753
|
# Loop over each PSD (not in parallel --> dask="forbidden")
|
|
331
754
|
# - It costs much more to initiate the scatterer rather than looping over timesteps !
|
|
332
755
|
da_radar = xr.apply_ufunc(
|
|
@@ -336,34 +759,140 @@ def get_empirical_radar_parameters(
|
|
|
336
759
|
input_core_dims=[["diameter_bin_center"]],
|
|
337
760
|
output_core_dims=[["radar_variables"]],
|
|
338
761
|
vectorize=True,
|
|
339
|
-
dask="
|
|
340
|
-
dask_gufunc_kwargs={
|
|
762
|
+
dask="parallelized",
|
|
763
|
+
dask_gufunc_kwargs={
|
|
764
|
+
"output_sizes": {"radar_variables": len(RADAR_VARIABLES)},
|
|
765
|
+
}, # lengths of the new output_core_dims dimensions.
|
|
341
766
|
output_dtypes=["float64"],
|
|
342
767
|
)
|
|
768
|
+
# Finalize radar dataset (add name, coordinates)
|
|
769
|
+
ds_radar = _finalize_radar_dataset(
|
|
770
|
+
da_radar=da_radar,
|
|
771
|
+
frequency=frequency,
|
|
772
|
+
num_points=num_points,
|
|
773
|
+
diameter_max=diameter_max,
|
|
774
|
+
canting_angle_std=canting_angle_std,
|
|
775
|
+
axis_ratio_model=axis_ratio_model,
|
|
776
|
+
permittivity_model=permittivity_model,
|
|
777
|
+
water_temperature=water_temperature,
|
|
778
|
+
elevation_angle=elevation_angle,
|
|
779
|
+
)
|
|
780
|
+
return ds_radar
|
|
343
781
|
|
|
782
|
+
|
|
783
|
+
def _finalize_radar_dataset(
|
|
784
|
+
da_radar,
|
|
785
|
+
frequency,
|
|
786
|
+
num_points,
|
|
787
|
+
diameter_max,
|
|
788
|
+
canting_angle_std,
|
|
789
|
+
axis_ratio_model,
|
|
790
|
+
permittivity_model,
|
|
791
|
+
water_temperature,
|
|
792
|
+
elevation_angle,
|
|
793
|
+
):
|
|
344
794
|
# Add parameters coordinates
|
|
345
|
-
da_radar = da_radar.assign_coords({"radar_variables":
|
|
795
|
+
da_radar = da_radar.assign_coords({"radar_variables": RADAR_VARIABLES})
|
|
346
796
|
|
|
347
797
|
# Create parameters dataset
|
|
348
798
|
ds_radar = da_radar.to_dataset(dim="radar_variables")
|
|
349
799
|
|
|
350
|
-
# Expand dimensions for later merging
|
|
800
|
+
# Expand dimensions for later merging in get_radar_parameters()
|
|
351
801
|
dims_dict = {
|
|
352
|
-
"
|
|
353
|
-
"axis_ratio": [axis_ratio],
|
|
354
|
-
"canting_angle_std": [canting_angle_std],
|
|
802
|
+
"frequency": [frequency],
|
|
355
803
|
"diameter_max": [diameter_max],
|
|
804
|
+
"num_points": [num_points],
|
|
805
|
+
"axis_ratio_model": [axis_ratio_model],
|
|
806
|
+
"canting_angle_std": [canting_angle_std],
|
|
807
|
+
"permittivity_model": [permittivity_model],
|
|
808
|
+
"water_temperature": [water_temperature],
|
|
809
|
+
"elevation_angle": [elevation_angle],
|
|
356
810
|
}
|
|
357
811
|
ds_radar = ds_radar.expand_dims(dim=dims_dict)
|
|
358
812
|
return ds_radar
|
|
359
813
|
|
|
360
814
|
|
|
815
|
+
####----------------------------------------------------------------------
|
|
816
|
+
#### Wrapper for L2E and L2M products
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def ensure_rounded_unique_array(arr, decimals=None):
|
|
820
|
+
"""Ensure that the input array is a unique, rounded array."""
|
|
821
|
+
arr = np.atleast_1d(arr)
|
|
822
|
+
if decimals is not None:
|
|
823
|
+
arr = arr.round(decimals)
|
|
824
|
+
return np.unique(arr)
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def get_list_simulations_params(
|
|
828
|
+
frequency,
|
|
829
|
+
num_points,
|
|
830
|
+
diameter_max,
|
|
831
|
+
canting_angle_std,
|
|
832
|
+
axis_ratio_model,
|
|
833
|
+
permittivity_model,
|
|
834
|
+
water_temperature,
|
|
835
|
+
elevation_angle,
|
|
836
|
+
):
|
|
837
|
+
"""Return list with the set of parameters required for each simulation."""
|
|
838
|
+
# Ensure numeric frequencies
|
|
839
|
+
frequency = ensure_numerical_frequency(frequency)
|
|
840
|
+
|
|
841
|
+
# Ensure arguments are unique set of values
|
|
842
|
+
# - Otherwise problems with non-unique xarray dataset coordinates
|
|
843
|
+
frequency = ensure_rounded_unique_array(frequency, decimals=2)
|
|
844
|
+
num_points = ensure_rounded_unique_array(num_points, decimals=0)
|
|
845
|
+
diameter_max = ensure_rounded_unique_array(diameter_max, decimals=1)
|
|
846
|
+
canting_angle_std = ensure_rounded_unique_array(canting_angle_std, decimals=1)
|
|
847
|
+
axis_ratio_model = ensure_rounded_unique_array(axis_ratio_model)
|
|
848
|
+
permittivity_model = ensure_rounded_unique_array(permittivity_model)
|
|
849
|
+
water_temperature = ensure_rounded_unique_array(water_temperature, decimals=1)
|
|
850
|
+
elevation_angle = ensure_rounded_unique_array(elevation_angle, decimals=1)
|
|
851
|
+
|
|
852
|
+
# Check parameters validity
|
|
853
|
+
axis_ratio_model = [check_axis_ratio_model(model) for model in axis_ratio_model]
|
|
854
|
+
permittivity_model = [check_permittivity_model(model) for model in permittivity_model]
|
|
855
|
+
|
|
856
|
+
# Order frequency from lowest to highest
|
|
857
|
+
# --> ['S', 'C', 'X', 'Ku', 'K', 'Ka', 'W']
|
|
858
|
+
frequency = sorted(frequency)
|
|
859
|
+
|
|
860
|
+
# Retrieve combination of parameters
|
|
861
|
+
list_params = [
|
|
862
|
+
{
|
|
863
|
+
"frequency": freq.item(),
|
|
864
|
+
"diameter_max": d_max.item(),
|
|
865
|
+
"num_points": n_p.item(),
|
|
866
|
+
"canting_angle_std": cas.item(),
|
|
867
|
+
"axis_ratio_model": ar.item(),
|
|
868
|
+
"permittivity_model": perm.item(),
|
|
869
|
+
"water_temperature": t_w.item(),
|
|
870
|
+
"elevation_angle": el.item(),
|
|
871
|
+
}
|
|
872
|
+
for freq, d_max, n_p, cas, ar, perm, t_w, el in itertools.product(
|
|
873
|
+
frequency,
|
|
874
|
+
diameter_max,
|
|
875
|
+
num_points,
|
|
876
|
+
canting_angle_std,
|
|
877
|
+
axis_ratio_model,
|
|
878
|
+
permittivity_model,
|
|
879
|
+
water_temperature,
|
|
880
|
+
elevation_angle,
|
|
881
|
+
)
|
|
882
|
+
]
|
|
883
|
+
return list_params
|
|
884
|
+
|
|
885
|
+
|
|
361
886
|
def get_radar_parameters(
|
|
362
887
|
ds,
|
|
363
|
-
|
|
364
|
-
|
|
888
|
+
frequency=None,
|
|
889
|
+
num_points=1024,
|
|
365
890
|
diameter_max=8,
|
|
366
|
-
|
|
891
|
+
canting_angle_std=7,
|
|
892
|
+
axis_ratio_model="Thurai2007",
|
|
893
|
+
permittivity_model="Turner2016",
|
|
894
|
+
water_temperature=10,
|
|
895
|
+
elevation_angle=0,
|
|
367
896
|
parallel=True,
|
|
368
897
|
):
|
|
369
898
|
"""Compute radar parameters from empirical drop number concentration or PSD model.
|
|
@@ -372,15 +901,31 @@ def get_radar_parameters(
|
|
|
372
901
|
----------
|
|
373
902
|
ds : xarray.Dataset
|
|
374
903
|
Dataset containing the drop number concentration variable.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
904
|
+
frequency : str, float, or list of str and float, optional
|
|
905
|
+
Frequencies in GHz for which to compute the radar parameters.
|
|
906
|
+
Alternatively, also strings can be used to specify common radar frequencies.
|
|
907
|
+
If ``None``, the common radar frequencies will be used.
|
|
908
|
+
See ``disdrodb.scattering.available_radar_bands()``.
|
|
909
|
+
num_points: int or lis tof integer, optional
|
|
910
|
+
Number of bins into which discretize the PSD.
|
|
380
911
|
diameter_max : float or list of float, optional
|
|
381
912
|
Maximum diameter. The default value is 8 mm.
|
|
382
|
-
|
|
383
|
-
|
|
913
|
+
canting_angle_std : float or list of float, optional
|
|
914
|
+
Standard deviation of the canting angle. The default value is 7.
|
|
915
|
+
axis_ratio_model : str or list of str, optional
|
|
916
|
+
Models to compute the axis ratio. The default model is ``Thurai2007``.
|
|
917
|
+
See available models with ``disdrodb.scattering.available_axis_ratio_models()``.
|
|
918
|
+
permittivity_model : str str or list of str, optional
|
|
919
|
+
Permittivity model to use to compute the refractive index and the
|
|
920
|
+
rayleigh_dielectric_factor. The default is ``Turner2016``.
|
|
921
|
+
See available models with ``disdrodb.scattering.available_permittivity_models()``.
|
|
922
|
+
water_temperature : float or list of float, optional
|
|
923
|
+
Water temperature in degree Celsius to be used in the permittivity model.
|
|
924
|
+
The default is 10 degC.
|
|
925
|
+
elevation_angle: float or list of float, optional
|
|
926
|
+
Radar elevation angles in degrees.
|
|
927
|
+
Specify 90 degrees for vertically pointing radars.
|
|
928
|
+
The default is 0 degrees.
|
|
384
929
|
parallel : bool, optional
|
|
385
930
|
Whether to compute radar variables in parallel.
|
|
386
931
|
The default value is ``True``.
|
|
@@ -393,43 +938,31 @@ def get_radar_parameters(
|
|
|
393
938
|
# Decide whether to simulate radar parameters based on empirical PSD or model PSD
|
|
394
939
|
if "disdrodb_psd_model" not in ds.attrs and "drop_number_concentration" not in ds:
|
|
395
940
|
raise ValueError("The input dataset is not a DISDRODB L2E or L2M product.")
|
|
941
|
+
|
|
396
942
|
# Model-based simulation
|
|
397
943
|
if "disdrodb_psd_model" in ds.attrs:
|
|
398
944
|
func = get_model_radar_parameters
|
|
399
|
-
ds_subset =
|
|
945
|
+
ds_subset = select_psd_parameters(ds)
|
|
400
946
|
# Empirical PSD simulation
|
|
401
947
|
else:
|
|
402
948
|
func = get_empirical_radar_parameters
|
|
403
|
-
ds_subset = ds[["drop_number_concentration"]]
|
|
404
|
-
|
|
405
|
-
# Initialize radar band if not provided
|
|
406
|
-
if radar_band is None:
|
|
407
|
-
radar_band = available_radar_bands()
|
|
949
|
+
ds_subset = ds[["drop_number_concentration"]]
|
|
408
950
|
|
|
409
|
-
#
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
axis_ratio = np.atleast_1d(axis_ratio)
|
|
413
|
-
radar_band = np.atleast_1d(radar_band)
|
|
951
|
+
# Define default frequencies if not specified
|
|
952
|
+
if frequency is None:
|
|
953
|
+
frequency = available_radar_bands()
|
|
414
954
|
|
|
415
|
-
#
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
"radar_band": rb.item(),
|
|
427
|
-
"canting_angle_std": cas.item(),
|
|
428
|
-
"axis_ratio": ar.item(),
|
|
429
|
-
"diameter_max": d_max.item(),
|
|
430
|
-
}
|
|
431
|
-
for rb, cas, ar, d_max in itertools.product(radar_band, canting_angle_std, axis_ratio, diameter_max)
|
|
432
|
-
]
|
|
955
|
+
# Define parameters for all requested simulations
|
|
956
|
+
list_params = get_list_simulations_params(
|
|
957
|
+
frequency=frequency,
|
|
958
|
+
num_points=num_points,
|
|
959
|
+
diameter_max=diameter_max,
|
|
960
|
+
canting_angle_std=canting_angle_std,
|
|
961
|
+
axis_ratio_model=axis_ratio_model,
|
|
962
|
+
permittivity_model=permittivity_model,
|
|
963
|
+
water_temperature=water_temperature,
|
|
964
|
+
elevation_angle=elevation_angle,
|
|
965
|
+
)
|
|
433
966
|
|
|
434
967
|
# Compute radar variables for each configuration in parallel
|
|
435
968
|
# - The function expects the data into memory (no dask arrays !)
|
|
@@ -440,17 +973,46 @@ def get_radar_parameters(
|
|
|
440
973
|
list_ds = [func(ds_subset, **params) for params in list_params]
|
|
441
974
|
|
|
442
975
|
# Merge into a single dataset
|
|
443
|
-
# - Order radar bands from longest to shortest wavelength
|
|
444
976
|
ds_radar = xr.merge(list_ds)
|
|
445
|
-
|
|
977
|
+
|
|
978
|
+
# Order frequency from lowest to highest
|
|
979
|
+
# --> ['S', 'C', 'X', 'Ku', 'K', 'Ka', 'W']
|
|
980
|
+
frequency = sorted(ds_radar["frequency"].to_numpy())
|
|
981
|
+
ds_radar = ds_radar.sel(frequency=frequency)
|
|
982
|
+
|
|
983
|
+
# Map default frequency to classical radar band
|
|
984
|
+
# --> This transform the frequency coordinate to dtype object
|
|
985
|
+
ds_radar = _replace_common_frequency_with_radar_band(ds_radar)
|
|
446
986
|
|
|
447
987
|
# Copy global attributes from input dataset
|
|
448
988
|
ds_radar.attrs = ds.attrs.copy()
|
|
449
989
|
|
|
450
|
-
# Remove single dimensions
|
|
451
|
-
|
|
452
|
-
for param in
|
|
990
|
+
# Remove single dimensions and add scattering settings information for single dimensions
|
|
991
|
+
scattering_string = ""
|
|
992
|
+
for param in RADAR_OPTIONS:
|
|
453
993
|
if ds_radar.sizes[param] == 1:
|
|
454
|
-
|
|
994
|
+
value = ds_radar[param].item()
|
|
995
|
+
scattering_string += f"param: {value}; "
|
|
996
|
+
|
|
997
|
+
if scattering_string != "":
|
|
998
|
+
ds_radar.attrs["disdrodb_scattering_options"] = scattering_string
|
|
455
999
|
ds_radar = ds_radar.squeeze()
|
|
456
1000
|
return ds_radar
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def _map_frequency_to_band(f):
|
|
1004
|
+
"""Function to map frequency value to radar band."""
|
|
1005
|
+
for band, val in frequency_dict.items():
|
|
1006
|
+
if np.isclose(f, val):
|
|
1007
|
+
return band
|
|
1008
|
+
return f
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def _replace_common_frequency_with_radar_band(ds_radar):
|
|
1012
|
+
"""Replace dataset coordinates with radar band if the case."""
|
|
1013
|
+
# Map frequencies to radar bands
|
|
1014
|
+
frequency = ds_radar["frequency"].to_numpy()
|
|
1015
|
+
frequency = [_map_frequency_to_band(f) for f in frequency]
|
|
1016
|
+
# Update dataset with new coordinate labels
|
|
1017
|
+
ds_radar = ds_radar.assign_coords({"frequency": ("frequency", frequency)})
|
|
1018
|
+
return ds_radar
|