gpmmatch 1.5.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.
- gpmmatch/__init__.py +34 -0
- gpmmatch/correct.py +416 -0
- gpmmatch/default.py +197 -0
- gpmmatch/gpmmatch.py +702 -0
- gpmmatch/io.py +462 -0
- gpmmatch-1.5.4.dist-info/METADATA +206 -0
- gpmmatch-1.5.4.dist-info/RECORD +10 -0
- gpmmatch-1.5.4.dist-info/WHEEL +5 -0
- gpmmatch-1.5.4.dist-info/licenses/LICENSE +201 -0
- gpmmatch-1.5.4.dist-info/top_level.txt +1 -0
gpmmatch/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GPM volume matching package.
|
|
3
|
+
|
|
4
|
+
Volume matching of ground radar and GPM satellite data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .gpmmatch import (
|
|
8
|
+
NoRainError,
|
|
9
|
+
generate_filename,
|
|
10
|
+
get_radar_coordinates,
|
|
11
|
+
get_gr_reflectivity,
|
|
12
|
+
has_valid_data,
|
|
13
|
+
volume_matching,
|
|
14
|
+
vmatch_multi_pass,
|
|
15
|
+
MIN_SAMPLE_POINTS,
|
|
16
|
+
MIN_REFL_SAMPLES,
|
|
17
|
+
MAX_OFFSET_THRESHOLD,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__author__ = "Valentin Louf"
|
|
21
|
+
__email__ = "valentin.louf@bom.gov.au"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"NoRainError",
|
|
25
|
+
"generate_filename",
|
|
26
|
+
"get_radar_coordinates",
|
|
27
|
+
"get_gr_reflectivity",
|
|
28
|
+
"has_valid_data",
|
|
29
|
+
"volume_matching",
|
|
30
|
+
"vmatch_multi_pass",
|
|
31
|
+
"MIN_SAMPLE_POINTS",
|
|
32
|
+
"MIN_REFL_SAMPLES",
|
|
33
|
+
"MAX_OFFSET_THRESHOLD",
|
|
34
|
+
]
|
gpmmatch/correct.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Various utilities for correction and conversion of satellite data.
|
|
3
|
+
|
|
4
|
+
@title: correct
|
|
5
|
+
@author: Valentin Louf <valentin.louf@bom.gov.au>
|
|
6
|
+
@institutions: Monash University and the Australian Bureau of Meteorology
|
|
7
|
+
@creation: 17/02/2020
|
|
8
|
+
@date: 12/05/2025
|
|
9
|
+
|
|
10
|
+
.. autosummary::
|
|
11
|
+
:toctree: generated/
|
|
12
|
+
|
|
13
|
+
compute_gaussian_curvature
|
|
14
|
+
convert_gpmrefl_grband_dfr
|
|
15
|
+
convert_gpmrefl_grband_phase_aware
|
|
16
|
+
correct_parallax
|
|
17
|
+
attenuation_correction_zphi
|
|
18
|
+
attenuation_correction_gunn_east
|
|
19
|
+
get_offset
|
|
20
|
+
grid_displacement
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Tuple
|
|
24
|
+
import numpy as np
|
|
25
|
+
import xarray as xr
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def compute_gaussian_curvature(lat0: float) -> float:
|
|
29
|
+
"""
|
|
30
|
+
Determine the Earth's Gaussian radius of curvature at the radar
|
|
31
|
+
https://en.wikipedia.org/wiki/Earth_radius#Radii_of_curvature
|
|
32
|
+
|
|
33
|
+
Parameter:
|
|
34
|
+
----------
|
|
35
|
+
lat0: float
|
|
36
|
+
Ground radar latitude.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
--------
|
|
40
|
+
ae: float
|
|
41
|
+
Scaled Gaussian radius.
|
|
42
|
+
"""
|
|
43
|
+
# Major and minor radii of the Ellipsoid
|
|
44
|
+
a = 6378137.0 # Earth radius in meters
|
|
45
|
+
e2 = 0.0066943800
|
|
46
|
+
b = a * np.sqrt(1 - e2)
|
|
47
|
+
|
|
48
|
+
tmp = (a * np.cos(np.pi / 180 * lat0)) ** 2 + (b * np.sin(np.pi / 180 * lat0)) ** 2 # Denominator
|
|
49
|
+
an = (a**2) / np.sqrt(tmp) # Radius of curvature in the prime vertical (east–west direction)
|
|
50
|
+
am = (a * b) ** 2 / tmp**1.5 # Radius of curvature in the north–south meridian
|
|
51
|
+
ag = np.sqrt(an * am) # Earth's Gaussian radius of curvature
|
|
52
|
+
ae = (4 / 3.0) * ag
|
|
53
|
+
|
|
54
|
+
return ae
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def convert_gpmrefl_grband_dfr(refl_gpm: np.ndarray, radar_band: str) -> np.ndarray:
|
|
58
|
+
"""
|
|
59
|
+
Convert GPM reflectivity to ground radar band using the DFR relationship
|
|
60
|
+
found in Louf et al. (2019) paper.
|
|
61
|
+
|
|
62
|
+
Parameters:
|
|
63
|
+
===========
|
|
64
|
+
refl_gpm:
|
|
65
|
+
Satellite reflectivity field.
|
|
66
|
+
radar_band: str
|
|
67
|
+
Possible values are 'S', 'C', or 'X'
|
|
68
|
+
|
|
69
|
+
Return:
|
|
70
|
+
=======
|
|
71
|
+
refl:
|
|
72
|
+
Reflectivity conversion from Ku-band to ground radar band
|
|
73
|
+
"""
|
|
74
|
+
if radar_band == "S":
|
|
75
|
+
cof = np.array([2.01236803e-07, -6.50694273e-06, 1.10885533e-03, -6.47985914e-02, -7.46518423e-02])
|
|
76
|
+
dfr = np.poly1d(cof)
|
|
77
|
+
elif radar_band == "C":
|
|
78
|
+
cof = np.array([1.21547932e-06, -1.23266138e-04, 6.38562875e-03, -1.52248868e-01, 5.33556919e-01])
|
|
79
|
+
dfr = np.poly1d(cof)
|
|
80
|
+
elif radar_band == "X":
|
|
81
|
+
# Use of C band DFR relationship multiply by ratio
|
|
82
|
+
cof = np.array([1.21547932e-06, -1.23266138e-04, 6.38562875e-03, -1.52248868e-01, 5.33556919e-01])
|
|
83
|
+
dfr = 3.2 / 5.5 * np.poly1d(cof)
|
|
84
|
+
else:
|
|
85
|
+
raise ValueError(f"Radar reflectivity band ({radar_band}) not supported.")
|
|
86
|
+
|
|
87
|
+
return refl_gpm + dfr(refl_gpm)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def convert_gpmrefl_grband_phase_aware(
|
|
91
|
+
refl_gpm: np.ndarray,
|
|
92
|
+
height_gpm: np.ndarray,
|
|
93
|
+
height_bb: np.ndarray,
|
|
94
|
+
radar_band: str,
|
|
95
|
+
bb_margin: float = 500.0,
|
|
96
|
+
) -> np.ndarray:
|
|
97
|
+
"""
|
|
98
|
+
Convert GPM Ku-band reflectivity to ground radar band using phase-aware DFR.
|
|
99
|
+
|
|
100
|
+
This function accounts for hydrometeor phase (ice, melting layer, or liquid)
|
|
101
|
+
by using the GPM bright band (melting layer) height. Different DFR relationships
|
|
102
|
+
are applied based on whether the radar gate is above, within, or below the
|
|
103
|
+
melting layer.
|
|
104
|
+
|
|
105
|
+
Parameters:
|
|
106
|
+
===========
|
|
107
|
+
refl_gpm: np.ndarray
|
|
108
|
+
GPM Ku-band reflectivity field (dBZ). Shape: (nscan, nray, nbin)
|
|
109
|
+
height_gpm: np.ndarray
|
|
110
|
+
Height of each GPM gate (meters). Shape: (nscan, nray, nbin)
|
|
111
|
+
height_bb: np.ndarray
|
|
112
|
+
Bright band (melting layer) height from GPM (meters). Shape: (nscan, nray)
|
|
113
|
+
Values < 0 indicate no bright band detected (all rain or all ice)
|
|
114
|
+
radar_band: str
|
|
115
|
+
Ground radar frequency band. Possible values: 'S', 'C', or 'X'
|
|
116
|
+
bb_margin: float, optional
|
|
117
|
+
Vertical margin around bright band height to define melting layer (meters).
|
|
118
|
+
Default is 500m (±500m around heightBB defines the melting layer).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
========
|
|
122
|
+
refl_grband: np.ndarray
|
|
123
|
+
Reflectivity converted from Ku-band to ground radar band (dBZ).
|
|
124
|
+
Same shape as refl_gpm.
|
|
125
|
+
|
|
126
|
+
References:
|
|
127
|
+
===========
|
|
128
|
+
- Rain DFR coefficients from Louf et al. (2019) J. Atmos. Oceanic Technol.
|
|
129
|
+
- Ice scattering theory: Tyynelä et al. (2011), Leinonen (2014)
|
|
130
|
+
- GPM bright band detection: Awaka et al. (2016)
|
|
131
|
+
"""
|
|
132
|
+
if radar_band not in ["S", "C", "X"]:
|
|
133
|
+
raise ValueError(f"Radar reflectivity band ({radar_band}) not supported. Use 'S', 'C', or 'X'.")
|
|
134
|
+
|
|
135
|
+
# Define rain DFR polynomial coefficients (from Louf et al. 2019)
|
|
136
|
+
if radar_band == "S":
|
|
137
|
+
rain_cof = np.array([2.01236803e-07, -6.50694273e-06, 1.10885533e-03, -6.47985914e-02, -7.46518423e-02])
|
|
138
|
+
elif radar_band == "C":
|
|
139
|
+
rain_cof = np.array([1.21547932e-06, -1.23266138e-04, 6.38562875e-03, -1.52248868e-01, 5.33556919e-01])
|
|
140
|
+
elif radar_band == "X":
|
|
141
|
+
rain_cof = np.array([1.21547932e-06, -1.23266138e-04, 6.38562875e-03, -1.52248868e-01, 5.33556919e-01])
|
|
142
|
+
rain_cof = rain_cof * (3.2 / 5.5)
|
|
143
|
+
|
|
144
|
+
rain_dfr = np.poly1d(rain_cof)
|
|
145
|
+
|
|
146
|
+
# Ice DFR - smaller frequency dependence for ice particles
|
|
147
|
+
# Based on Mie scattering theory for ice spheres
|
|
148
|
+
# Ice has less frequency-dependent scattering than rain
|
|
149
|
+
ice_dfr_offset = {
|
|
150
|
+
'S': 0.3, # dB - small positive offset for S-band
|
|
151
|
+
'C': 0.5, # dB - moderate offset for C-band
|
|
152
|
+
'X': 0.8 # dB - larger offset for X-band
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
refl_grband = np.full_like(refl_gpm, np.nan, dtype=np.float64)
|
|
156
|
+
# Expand heightBB to match 3D shape (nscan, nray, nbin)
|
|
157
|
+
height_bb_3d = np.broadcast_to(height_bb[..., np.newaxis], refl_gpm.shape)
|
|
158
|
+
|
|
159
|
+
no_bb_mask = height_bb_3d < 0
|
|
160
|
+
liquid_mask = (height_bb_3d >= 0) & (height_gpm < (height_bb_3d - bb_margin))
|
|
161
|
+
ice_mask = (height_bb_3d >= 0) & (height_gpm > (height_bb_3d + bb_margin))
|
|
162
|
+
melting_mask = (height_bb_3d >= 0) & (height_gpm >= (height_bb_3d - bb_margin)) & (height_gpm <= (height_bb_3d + bb_margin))
|
|
163
|
+
|
|
164
|
+
if np.any(no_bb_mask):
|
|
165
|
+
refl_grband[no_bb_mask] = refl_gpm[no_bb_mask] + rain_dfr(refl_gpm[no_bb_mask])
|
|
166
|
+
|
|
167
|
+
if np.any(liquid_mask):
|
|
168
|
+
refl_grband[liquid_mask] = refl_gpm[liquid_mask] + rain_dfr(refl_gpm[liquid_mask])
|
|
169
|
+
|
|
170
|
+
if np.any(ice_mask):
|
|
171
|
+
refl_grband[ice_mask] = refl_gpm[ice_mask] + ice_dfr_offset[radar_band]
|
|
172
|
+
|
|
173
|
+
if np.any(melting_mask):
|
|
174
|
+
# Calculate normalized height within melting layer (0 = bottom, 1 = top)
|
|
175
|
+
height_in_ml = height_gpm[melting_mask]
|
|
176
|
+
bb_height = height_bb_3d[melting_mask]
|
|
177
|
+
|
|
178
|
+
# Fraction of liquid (1 at bottom, 0 at top of melting layer)
|
|
179
|
+
liquid_fraction = 1.0 - (height_in_ml - (bb_height - bb_margin)) / (2 * bb_margin)
|
|
180
|
+
liquid_fraction = np.clip(liquid_fraction, 0, 1)
|
|
181
|
+
|
|
182
|
+
# Interpolate between ice and rain DFR
|
|
183
|
+
dfr_ice = ice_dfr_offset[radar_band]
|
|
184
|
+
dfr_rain = rain_dfr(refl_gpm[melting_mask])
|
|
185
|
+
dfr_mixed = liquid_fraction * dfr_rain + (1 - liquid_fraction) * dfr_ice
|
|
186
|
+
|
|
187
|
+
refl_grband[melting_mask] = refl_gpm[melting_mask] + dfr_mixed
|
|
188
|
+
|
|
189
|
+
return refl_grband
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def attenuation_correction_zphi(zh: np.ndarray, kdp: np.ndarray, dr: float = 1, wavelength: str = "C") -> np.ndarray:
|
|
193
|
+
"""
|
|
194
|
+
Attenuation correction using the ZPHI method.
|
|
195
|
+
|
|
196
|
+
Parameters:
|
|
197
|
+
-----------
|
|
198
|
+
zh : array-like
|
|
199
|
+
Horizontal reflectivity (dBZ)
|
|
200
|
+
kdp : array-like
|
|
201
|
+
Specific differential phase (deg/km)
|
|
202
|
+
dr : float
|
|
203
|
+
Range gate spacing in km
|
|
204
|
+
wavelength : str
|
|
205
|
+
'C' for C-band (~5.3 cm) or 'X' for X-band (~3.2 cm)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
--------
|
|
209
|
+
zh_corrected : array
|
|
210
|
+
Attenuation-corrected horizontal reflectivity (dBZ)
|
|
211
|
+
"""
|
|
212
|
+
if dr > 10:
|
|
213
|
+
raise ValueError("Range gate spacing 'dr' seems too large (>10 km). Check input.")
|
|
214
|
+
|
|
215
|
+
if wavelength.upper() == "C":
|
|
216
|
+
# C-band coefficients (Bringi et al. 2001)
|
|
217
|
+
alpha_h = 0.08 # coefficient for Ah = alpha * Kdp^beta
|
|
218
|
+
beta_h = 0.93
|
|
219
|
+
|
|
220
|
+
elif wavelength.upper() == "X":
|
|
221
|
+
# X-band coefficients (Park et al. 2005)
|
|
222
|
+
alpha_h = 0.28
|
|
223
|
+
beta_h = 0.95
|
|
224
|
+
else:
|
|
225
|
+
raise ValueError("Wavelength must be 'C' or 'X'")
|
|
226
|
+
|
|
227
|
+
# Calculate specific attenuation
|
|
228
|
+
ah = alpha_h * np.power(np.abs(kdp), beta_h) * np.sign(kdp)
|
|
229
|
+
PIA_h = np.nancumsum(ah * dr, axis=1) # Path Integrated Attenuation for Zh
|
|
230
|
+
zh_corrected = zh + 2 * PIA_h # Factor of 2 for two-way path
|
|
231
|
+
|
|
232
|
+
return zh_corrected
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def attenuation_correction_gunn_east(
|
|
236
|
+
zh: np.ndarray, dr: float = 1, wavelength: str = "C", max_thld: float = 10
|
|
237
|
+
) -> np.ndarray:
|
|
238
|
+
"""
|
|
239
|
+
Attenuation correction using Gunn and East (1954) method.
|
|
240
|
+
This is based on the power-law relationship.
|
|
241
|
+
Eq. 2.66 p 106 of radar meteorology by Henri Sauvagot.
|
|
242
|
+
|
|
243
|
+
Parameters:
|
|
244
|
+
-----------
|
|
245
|
+
zh : array-like
|
|
246
|
+
Horizontal reflectivity in dBZ along a radial
|
|
247
|
+
dr : float
|
|
248
|
+
Range gate spacing in km
|
|
249
|
+
wavelength : str
|
|
250
|
+
'C' for C-band or 'X' for X-band
|
|
251
|
+
max_thld: float
|
|
252
|
+
Capped-attenuation correction to avoid blowing up the refl in dB.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
--------
|
|
256
|
+
zh_corrected : array
|
|
257
|
+
Attenuation-corrected reflectivity (dBZ)
|
|
258
|
+
"""
|
|
259
|
+
if dr > 10:
|
|
260
|
+
raise ValueError("Range gate spacing 'dr' seems too large (>10 km). Check input.")
|
|
261
|
+
if wavelength.upper() == "C":
|
|
262
|
+
l = 5.5
|
|
263
|
+
elif wavelength.upper() == "X":
|
|
264
|
+
l = 3.2
|
|
265
|
+
else:
|
|
266
|
+
raise ValueError("Wavelength must be 'C' or 'X'")
|
|
267
|
+
|
|
268
|
+
# Convert dBZ to linear Z (mm^6/m^3)
|
|
269
|
+
Z_linear = 10 ** (zh / 10.0)
|
|
270
|
+
R = (Z_linear / 200) ** (1 / 1.6)
|
|
271
|
+
ap = 0.35e-2 * R**1.6 / (l**4) + 0.22e-2 * R / l
|
|
272
|
+
|
|
273
|
+
PIA_h = np.nancumsum(ap * dr, axis=1)
|
|
274
|
+
PIA_h[PIA_h > max_thld / 2] = max_thld / 2
|
|
275
|
+
Z_corrected = zh + 2 * PIA_h
|
|
276
|
+
|
|
277
|
+
return Z_corrected
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def correct_parallax(
|
|
281
|
+
sr_x: np.ndarray, sr_y: np.ndarray, gpmset: xr.Dataset
|
|
282
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
283
|
+
"""
|
|
284
|
+
Adjust the geo-locations of the SR pixels. The `sr_xy` coordinates of the
|
|
285
|
+
SR beam footprints need to be in the azimuthal equidistant projection of
|
|
286
|
+
the ground radar. This ensures that the ground radar is fixed at
|
|
287
|
+
xy-coordinate (0, 0), and every SR bin has its relative xy-coordinates
|
|
288
|
+
with respect to the ground radar site.
|
|
289
|
+
|
|
290
|
+
Parameters:
|
|
291
|
+
-----------
|
|
292
|
+
sr_x: ndarray
|
|
293
|
+
Array of x-coordinates of shape (nscans, nbeams)
|
|
294
|
+
sr_y: ndarray
|
|
295
|
+
Array of y-coordinates of shape (nscans, nbeams)
|
|
296
|
+
gpmset: xarray
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
--------
|
|
300
|
+
sr_xyp : :class:`numpy:numpy.ndarray`
|
|
301
|
+
Array of parallax corrected coordinates
|
|
302
|
+
of shape (nscans, nbeams, nbins).
|
|
303
|
+
z_sr : :class:`numpy:numpy.ndarray`
|
|
304
|
+
Array of SR bin altitudes of shape (nscans, nbeams, nbins).
|
|
305
|
+
"""
|
|
306
|
+
r_sr_inv, alpha = gpmset.nbin.values, gpmset.nray.values
|
|
307
|
+
# calculate height of bin
|
|
308
|
+
z = r_sr_inv * np.cos(np.deg2rad(alpha))[..., np.newaxis]
|
|
309
|
+
z_sr = np.repeat(z[np.newaxis, :, :], len(gpmset.nscan), axis=0)
|
|
310
|
+
# calculate bin ground xy-displacement length
|
|
311
|
+
ds = r_sr_inv * np.sin(np.deg2rad(alpha))[..., np.newaxis]
|
|
312
|
+
|
|
313
|
+
# calculate x,y-differences between ground coordinate
|
|
314
|
+
# and center ground coordinate [25th element]
|
|
315
|
+
center = int(np.floor(len(sr_x[-1]) / 2.0))
|
|
316
|
+
xdiff = sr_x - sr_x[:, center][:, np.newaxis]
|
|
317
|
+
ydiff = sr_y - sr_y[:, center][:, np.newaxis]
|
|
318
|
+
|
|
319
|
+
# assuming ydiff and xdiff being a triangles adjacent and
|
|
320
|
+
# opposite this calculates the xy-angle of the SR scan
|
|
321
|
+
ang = np.arctan2(ydiff, xdiff)
|
|
322
|
+
|
|
323
|
+
# calculate displacement dx, dy from displacement length
|
|
324
|
+
dx = ds * np.cos(ang)[..., np.newaxis]
|
|
325
|
+
dy = ds * np.sin(ang)[..., np.newaxis]
|
|
326
|
+
|
|
327
|
+
# subtract displacement from SR ground coordinates
|
|
328
|
+
sr_xp = sr_x[..., np.newaxis] - dx
|
|
329
|
+
sr_yp = sr_y[..., np.newaxis] - dy
|
|
330
|
+
|
|
331
|
+
return sr_xp, sr_yp, z_sr
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def get_offset(matchset: xr.Dataset, dr: float, nbins: int = 200) -> float:
|
|
335
|
+
"""
|
|
336
|
+
Compute the Offset between GR and GPM. It will try to compute the mode of
|
|
337
|
+
the distribution and if it fails, then it will use the mean.
|
|
338
|
+
|
|
339
|
+
Parameter:
|
|
340
|
+
==========
|
|
341
|
+
matchset: xr.Dataset
|
|
342
|
+
Dataset of volume matching.
|
|
343
|
+
dr: float
|
|
344
|
+
Ground radar gate spacing (m).
|
|
345
|
+
nbins: int
|
|
346
|
+
Defines the number of equal-width bins in the distribution.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
========
|
|
350
|
+
offset: float
|
|
351
|
+
Offset between GR and GPM
|
|
352
|
+
"""
|
|
353
|
+
offset = np.arange(-15, 15, 0.2, dtype=np.float64)
|
|
354
|
+
area = np.zeros_like(offset)
|
|
355
|
+
|
|
356
|
+
refl_gpm = matchset.refl_gpm_grband.values.flatten().copy()
|
|
357
|
+
refl_gr = matchset.refl_gr_weigthed.values.flatten().copy()
|
|
358
|
+
fmin = matchset.fmin_gr.values.flatten().copy()
|
|
359
|
+
|
|
360
|
+
pos = (refl_gpm > 36) | (refl_gr > 36) | (fmin != 1)
|
|
361
|
+
if (~pos).sum() == 0: # Relaxing fmin parameter.
|
|
362
|
+
pos = (refl_gpm > 36) | (refl_gr > 36) | (fmin < 0.9)
|
|
363
|
+
if (~pos).sum() == 0:
|
|
364
|
+
pos = (refl_gpm > 36) | (refl_gr > 36) | (fmin < 0.7)
|
|
365
|
+
|
|
366
|
+
refl_gpm[pos] = np.nan
|
|
367
|
+
refl_gr[pos] = np.nan
|
|
368
|
+
|
|
369
|
+
pdf_gpm, _ = np.histogram(refl_gpm, range=(0, 50), bins=nbins, density=True)
|
|
370
|
+
for idx, a in enumerate(offset):
|
|
371
|
+
pdf_gr, _ = np.histogram(refl_gr - a, range=(0, 50), bins=nbins, density=True)
|
|
372
|
+
diff = np.min([pdf_gr, pdf_gpm], axis=0)
|
|
373
|
+
area[idx] = np.sum(diff)
|
|
374
|
+
|
|
375
|
+
smoothed_area = np.convolve([1] * 12, area, "same")
|
|
376
|
+
maxpos = np.argmax(smoothed_area)
|
|
377
|
+
gr_offset = offset[maxpos]
|
|
378
|
+
return gr_offset
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def grid_displacement(field1: np.ndarray, field2: np.ndarray) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
|
382
|
+
"""
|
|
383
|
+
Calculate the grid displacement using Phase correlation.
|
|
384
|
+
http://en.wikipedia.org/wiki/Phase_correlation
|
|
385
|
+
|
|
386
|
+
Parameters:
|
|
387
|
+
-----------
|
|
388
|
+
field1, field2 : ndarray
|
|
389
|
+
Fields separated in time.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
--------
|
|
393
|
+
displacement : two-tuple
|
|
394
|
+
integers if pixels, otherwise floats. Result of the calculation
|
|
395
|
+
"""
|
|
396
|
+
# create copies of the data
|
|
397
|
+
ige1 = np.ma.masked_invalid(10 ** (field1 / 10)).filled(0)
|
|
398
|
+
ige2 = np.ma.masked_invalid(10 ** (field2 / 10)).filled(0)
|
|
399
|
+
|
|
400
|
+
# discrete fast fourier transformation and complex conjugation of image 2
|
|
401
|
+
image1FFT = np.fft.fft2(ige1)
|
|
402
|
+
image2FFT = np.conjugate(np.fft.fft2(ige2))
|
|
403
|
+
|
|
404
|
+
# inverse fourier transformation of product -> equal to cross correlation
|
|
405
|
+
imageCCor = np.real(np.fft.ifft2((image1FFT * image2FFT)))
|
|
406
|
+
|
|
407
|
+
# Shift the zero-frequency component to the center of the spectrum
|
|
408
|
+
imageCCorShift = np.fft.fftshift(imageCCor)
|
|
409
|
+
row, col = ige1.shape
|
|
410
|
+
|
|
411
|
+
# find the peak in the correlation
|
|
412
|
+
yShift, xShift = np.unravel_index(np.argmax(imageCCorShift), (row, col))
|
|
413
|
+
yShift -= row // 2
|
|
414
|
+
xShift -= col // 2
|
|
415
|
+
|
|
416
|
+
return (xShift, yShift)
|
gpmmatch/default.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Volume matching of ground radar and GPM satellite. Default naming and attribute.
|
|
3
|
+
|
|
4
|
+
@title: gpmmatch
|
|
5
|
+
@author: Valentin Louf <valentin.louf@bom.gov.au>
|
|
6
|
+
@institutions: Monash University and the Australian Bureau of Meteorology
|
|
7
|
+
@creation: 24/02/2020
|
|
8
|
+
@date: 29/05/2025
|
|
9
|
+
|
|
10
|
+
.. autosummary::
|
|
11
|
+
:toctree: generated/
|
|
12
|
+
|
|
13
|
+
gpmset_metadata
|
|
14
|
+
get_metadata
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def gpmset_metadata() -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Return a bunch of metadata (description, units, long_name, etc.) for the
|
|
23
|
+
GPM set metadata.
|
|
24
|
+
This metadata is used to describe the GPM data columns that are
|
|
25
|
+
volume-matched to the ground radar data.
|
|
26
|
+
The metadata includes information about the GPM overpass time, parallax
|
|
27
|
+
corrected coordinates, precipitation inside the ground radar scope,
|
|
28
|
+
range and elevation from the ground radar, and reflectivity in the
|
|
29
|
+
ground radar band.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
========
|
|
33
|
+
metadata: dict
|
|
34
|
+
"""
|
|
35
|
+
metadata = {
|
|
36
|
+
"overpass_time": {"description": "GPM overpass time at the closest from ground radar site"},
|
|
37
|
+
"x": {
|
|
38
|
+
"units": "m",
|
|
39
|
+
"description": "x-axis parallax corrected coordinates in relation to ground radar.",
|
|
40
|
+
},
|
|
41
|
+
"y": {
|
|
42
|
+
"units": "m",
|
|
43
|
+
"description": "y-axis parallax corrected coordinates in relation to ground radar.",
|
|
44
|
+
},
|
|
45
|
+
"z": {
|
|
46
|
+
"units": "m",
|
|
47
|
+
"description": "z-axis parallax corrected coordinates in relation to ground radar.",
|
|
48
|
+
},
|
|
49
|
+
"precip_in_gr_domain": {
|
|
50
|
+
"units": "1",
|
|
51
|
+
"description": "Satellite data-columns with precipitation inside the ground radar scope.",
|
|
52
|
+
},
|
|
53
|
+
"range_from_gr": {
|
|
54
|
+
"units": "m",
|
|
55
|
+
"description": "Range from satellite bins in relation to ground radar",
|
|
56
|
+
},
|
|
57
|
+
"elev_from_gr": {
|
|
58
|
+
"units": "degrees",
|
|
59
|
+
"description": "Elevation from satellite bins in relation to ground radar",
|
|
60
|
+
},
|
|
61
|
+
"reflectivity_grband": {"units": "dBZ"},
|
|
62
|
+
}
|
|
63
|
+
return metadata
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_metadata() -> Dict[str, Dict[str, str]]:
|
|
67
|
+
"""
|
|
68
|
+
Return a bunch of metadata (description, units, long_name, etc.) for the
|
|
69
|
+
output dataset.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
========
|
|
73
|
+
metadata: dict
|
|
74
|
+
"""
|
|
75
|
+
metadata = {
|
|
76
|
+
"fmin_gpm": {
|
|
77
|
+
"units": "",
|
|
78
|
+
"long_name": "fmin_gpm",
|
|
79
|
+
"description": "FMIN ratio for GPM.",
|
|
80
|
+
},
|
|
81
|
+
"fmin_gr": {
|
|
82
|
+
"units": "",
|
|
83
|
+
"long_name": "fmin_gr",
|
|
84
|
+
"description": "FMIN ratio for GR.",
|
|
85
|
+
},
|
|
86
|
+
"refl_gpm_raw": {
|
|
87
|
+
"units": "dBZ",
|
|
88
|
+
"long_name": "GPM_reflectivity",
|
|
89
|
+
"description": "GPM reflectivity volume-matched to ground radar.",
|
|
90
|
+
},
|
|
91
|
+
"refl_gpm_grband": {
|
|
92
|
+
"units": "dBZ",
|
|
93
|
+
"long_name": "GPM_reflectivity_grband_stratiform",
|
|
94
|
+
"description": "GPM reflectivity converted to ground radar frequency band.",
|
|
95
|
+
},
|
|
96
|
+
"refl_gr_raw": {
|
|
97
|
+
"units": "dBZ",
|
|
98
|
+
"long_name": "reflectivity",
|
|
99
|
+
"description": "Ground radar reflectivity volume matched using a `normal` average.",
|
|
100
|
+
},
|
|
101
|
+
"refl_gr_weigthed": {
|
|
102
|
+
"units": "dBZ",
|
|
103
|
+
"long_name": "reflectivity",
|
|
104
|
+
"description": "Ground radar reflectivity volume matched using a distance-weighted average.",
|
|
105
|
+
},
|
|
106
|
+
"std_refl_gpm": {
|
|
107
|
+
"units": "dB",
|
|
108
|
+
"long_name": "standard_deviation_reflectivity",
|
|
109
|
+
"description": "GPM reflectivity standard deviation of the volume-matched sample.",
|
|
110
|
+
},
|
|
111
|
+
"std_refl_gr": {
|
|
112
|
+
"units": "dB",
|
|
113
|
+
"long_name": "standard_deviation_reflectivity",
|
|
114
|
+
"description": "Ground radar reflectivity standard deviation of the volume-matched sample.",
|
|
115
|
+
},
|
|
116
|
+
"sample_gpm": {
|
|
117
|
+
"units": "1",
|
|
118
|
+
"long_name": "sample_size",
|
|
119
|
+
"description": "Number of GPM bins used to compute the volume-matched pixels at a given points",
|
|
120
|
+
},
|
|
121
|
+
"reject_gpm": {
|
|
122
|
+
"units": "1",
|
|
123
|
+
"long_name": "rejected_sample_size",
|
|
124
|
+
"description": "Number of GPM bins rejected to compute the volume-matched pixels at a given points",
|
|
125
|
+
},
|
|
126
|
+
"sample_gr": {
|
|
127
|
+
"units": "1",
|
|
128
|
+
"long_name": "sample_size",
|
|
129
|
+
"description": "Number of ground-radar bins used to compute the volume-matched pixels at a given points",
|
|
130
|
+
},
|
|
131
|
+
"reject_gr": {
|
|
132
|
+
"units": "1",
|
|
133
|
+
"long_name": "rejected_sample_size",
|
|
134
|
+
"description": "Number of ground-radar bins rejected to compute the volume-matched pixels at a given points",
|
|
135
|
+
},
|
|
136
|
+
"volume_match_gpm": {
|
|
137
|
+
"units": "m^3",
|
|
138
|
+
"long_name": "volume",
|
|
139
|
+
"description": "Volume of the GPM sample for each match points.",
|
|
140
|
+
},
|
|
141
|
+
"volume_match_gr": {
|
|
142
|
+
"units": "m^3",
|
|
143
|
+
"long_name": "volume",
|
|
144
|
+
"description": "Volume of the ground radar sample for each match points.",
|
|
145
|
+
},
|
|
146
|
+
"x": {
|
|
147
|
+
"units": "m",
|
|
148
|
+
"long_name": "projected_x_axis_coordinates",
|
|
149
|
+
"projection": "Azimuthal Equidistant from ground radar.",
|
|
150
|
+
},
|
|
151
|
+
"y": {
|
|
152
|
+
"units": "m",
|
|
153
|
+
"long_name": "projected_y_axis_coordinates",
|
|
154
|
+
"projection": "Azimuthal Equidistant from ground radar.",
|
|
155
|
+
},
|
|
156
|
+
"z": {
|
|
157
|
+
"units": "m",
|
|
158
|
+
"long_name": "projected_z_axis_coordinates",
|
|
159
|
+
"projection": "Azimuthal Equidistant from ground radar.",
|
|
160
|
+
},
|
|
161
|
+
"r": {
|
|
162
|
+
"units": "m",
|
|
163
|
+
"long_name": "range",
|
|
164
|
+
"description": "Range from ground radar.",
|
|
165
|
+
},
|
|
166
|
+
"timedelta": {
|
|
167
|
+
"units": "ns",
|
|
168
|
+
"long_name": "timedelta",
|
|
169
|
+
"description": "Maximum time delta between ground radar and GPM volumes.",
|
|
170
|
+
},
|
|
171
|
+
"elevation_gr": {
|
|
172
|
+
"units": "degrees",
|
|
173
|
+
"long_name": "elevation",
|
|
174
|
+
"description": "Ground radar reference elevation.",
|
|
175
|
+
},
|
|
176
|
+
"ntilt": {
|
|
177
|
+
"units": "1",
|
|
178
|
+
"long_name": "ground_radar_tilt_number",
|
|
179
|
+
"description": "Number of ground radar tilts used for volume matching.",
|
|
180
|
+
},
|
|
181
|
+
"nprof": {
|
|
182
|
+
"units": "1",
|
|
183
|
+
"long_name": "gpm_profile_number",
|
|
184
|
+
"description": "Number of GPM profiles (nrays x nscan) used for volume matching.",
|
|
185
|
+
},
|
|
186
|
+
"pir_gpm": {
|
|
187
|
+
"units": "dB m-1",
|
|
188
|
+
"long_name": "GPM_path_integrated_reflectivity",
|
|
189
|
+
"description": "Path integrated GPM reflectivity volume-matched.",
|
|
190
|
+
},
|
|
191
|
+
"pir_gr": {
|
|
192
|
+
"units": "dB m-1",
|
|
193
|
+
"long_name": "GR_path_integrated_reflectivity",
|
|
194
|
+
"description": "Path integrated GR reflectivity volume-matched.",
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
return metadata
|