Skip to content

Commit a6146a8

Browse files
committed
Merge branch 'master' of https://github.com/pySTEPS/pysteps
2 parents 8ffbf40 + 3ca053d commit a6146a8

File tree

9 files changed

+122
-88
lines changed

9 files changed

+122
-88
lines changed

examples/ensemble_verification.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@
6262
"n_cascade_levels" : [6],
6363
"noise_adjustment" : [True],
6464
"conditional" : [False],
65-
"precip_mask" : [True],
6665
"mask_method" : ["incremental"], # obs, incremental, sprog
67-
"prob_matching" : [True],
6866
}
6967

7068
# Conditional parameters
@@ -252,9 +250,7 @@ def export(X):
252250
noise_method=p["noise_method"],
253251
noise_stddev_adj=p["noise_adjustment"],
254252
ar_order=p["ar_order"],conditional=p["conditional"],
255-
use_probmatching=p["prob_matching"],
256-
mask_method=p["mask_method"],
257-
use_precip_mask=p["precip_mask"], callback=export,
253+
mask_method=p["mask_method"], callback=export,
258254
return_output=False, seed=p["seed"])
259255

260256
## save results

examples/run_ensemble_nowcast.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@
5353
ar_order = 2
5454
r_threshold = 0.1 # rain/no-rain threshold [mm/h]
5555
adjust_noise = True
56-
prob_matching = True
57-
precip_mask = True
5856
mask_method = "incremental" # sprog, obs or incremental
5957
conditional = False
6058
unit = "mm/h" # mm/h or dBZ
@@ -117,8 +115,7 @@
117115
bandpass_filter_method=bandpass_filter,
118116
noise_method=noise_method, noise_stddev_adj=adjust_noise,
119117
ar_order=ar_order, conditional=conditional,
120-
use_precip_mask=precip_mask, mask_method=mask_method,
121-
use_probmatching=prob_matching, seed=seed)
118+
mask_method=mask_method, seed=seed)
122119

123120
## if necessary, transform back all data
124121
R_fct, _ = transformer(R_fct, metadata, inverse=True)

pysteps/io/archive.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def find_by_date(date, root_path, path_fmt, fn_pattern, fn_ext, timestep,
1515
root_path : str
1616
The root path to search the input files.
1717
path_fmt : str
18-
Path format. It may consist of directory names separated by '/' and
19-
date/time specifiers beginning with '%' (e.g. %Y/%m/%d).
18+
Path format. It may consist of directory names separated by '/',
19+
date/time specifiers beginning with '%' (e.g. %Y/%m/%d) and wildcards
20+
(?) that match any single character.
2021
fn_pattern : str
2122
The name pattern of the input files without extension. The pattern can
2223
contain time specifiers (e.g. %H, %M and %S).

pysteps/io/importers.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ def import_bom_rf3(filename, **kwargs):
128128
metadata["unit"] = "mm/h"
129129
metadata["transform"] = None
130130
metadata["zerovalue"] = np.nanmin(R)
131-
metadata["threshold"] = np.nanmin(R[R>np.nanmin(R)])
131+
if np.any(np.isfinite(R)):
132+
metadata["threshold"] = np.nanmin(R[R>np.nanmin(R)])
133+
else:
134+
metadata["threshold"] = np.nan
132135

133136
return R, None, metadata
134137

@@ -255,7 +258,10 @@ def import_fmi_pgm(filename, **kwargs):
255258
metadata["unit"] = "dBZ"
256259
metadata["transform"] = "dB"
257260
metadata["zerovalue"] = np.nanmin(R)
258-
metadata["threshold"] = np.nanmin(R[R>np.nanmin(R)])
261+
if np.any(np.isfinite(R)):
262+
metadata["threshold"] = np.nanmin(R[R>np.nanmin(R)])
263+
else:
264+
metadata["threshold"] = np.nan
259265

260266
return R,None,metadata
261267

@@ -431,7 +437,7 @@ def import_mch_gif(filename, **kwargs):
431437
if np.any(R>np.nanmin(R)):
432438
metadata["threshold"] = np.nanmin(R[R>np.nanmin(R)])
433439
else:
434-
metadata["threshold"] = None
440+
metadata["threshold"] = np.nan
435441
metadata["institution"] = "MeteoSwiss"
436442
metadata["product"] = product
437443

@@ -538,14 +544,19 @@ def import_mch_hdf5(filename, **kwargs):
538544
unit = "mm/h"
539545
transform = None
540546

547+
if np.any(np.isfinite(R)):
548+
thr = np.nanmin(R[R>np.nanmin(R)])
549+
else:
550+
thr = np.nan
551+
541552
metadata.update({
542553
"yorigin":"upper",
543554
"institution":"MeteoSwiss",
544555
"accutime":5.,
545556
"unit":unit,
546557
"transform":transform,
547558
"zerovalue":np.nanmin(R),
548-
"threshold":np.nanmin(R[R>np.nanmin(R)]) })
559+
"threshold":thr })
549560

550561
f.close()
551562

@@ -778,6 +789,11 @@ def import_odim_hdf5(filename, **kwargs):
778789
unit = "mm/h"
779790
transform = None
780791

792+
if np.any(np.isfinite(R)):
793+
thr = np.nanmin(R[R>np.nanmin(R)])
794+
else:
795+
thr = nan
796+
781797
metadata = {"projection":proj4str,
782798
"ll_lon":LL_lon,
783799
"ll_lat":LL_lat,
@@ -795,7 +811,7 @@ def import_odim_hdf5(filename, **kwargs):
795811
"unit":unit,
796812
"transform":transform,
797813
"zerovalue":np.nanmin(R),
798-
"threshold":np.nanmin(R[R>np.nanmin(R)])}
814+
"threshold":thr}
799815

800816
f.close()
801817

pysteps/noise/motion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ def initialize_bps(V, pixelsperkm, timestep, p_pert_par=None, p_pert_perp=None,
3838
randstate=np.random, seed=None):
3939
"""Initialize the motion field perturbator described in :cite:`BPS2006`.
4040
For simplicity, the bias adjustment procedure described there has not been
41-
implemented. The perturbator generates a constant field whose magnitude
42-
depends on lead time.
41+
implemented. The perturbator generates a field whose magnitude increases
42+
with respect to lead time.
4343
4444
Parameters
4545
----------

pysteps/nowcasts/steps.py

Lines changed: 67 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
1919
kmperpixel=None, timestep=None, extrap_method="semilagrangian",
2020
decomp_method="fft", bandpass_filter_method="gaussian",
2121
noise_method="nonparametric", noise_stddev_adj=False, ar_order=2,
22-
vel_pert_method=None, conditional=False, use_precip_mask=True,
23-
use_probmatching=True, mask_method="incremental", callback=None,
24-
return_output=True, seed=None, num_workers=None, fft_method="numpy",
25-
extrap_kwargs={}, filter_kwargs={}, noise_kwargs={}, vel_pert_kwargs={}):
22+
vel_pert_method="bps", conditional=False, probmatching_method="cdf",
23+
mask_method="incremental", callback=None, return_output=True,
24+
seed=None, num_workers=None, fft_method="numpy", extrap_kwargs={},
25+
filter_kwargs={}, noise_kwargs={}, vel_pert_kwargs={}):
2626
"""Generate a nowcast ensemble by using the Short-Term Ensemble Prediction
2727
System (STEPS) method.
2828
@@ -47,7 +47,7 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
4747
----------------
4848
R_thr : float
4949
Specifies the threshold value for minimum observable precipitation
50-
intensity. Must be set if use_probmatching is True or conditional is True.
50+
intensity. Required if mask_method is not None or conditional is True.
5151
kmperpixel : float
5252
Spatial resolution of the input data (kilometers/pixel). Required if
5353
vel_pert_method is not None or mask_method is 'incremental'.
@@ -78,21 +78,20 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
7878
field is not perturbed.
7979
conditional : bool
8080
If set to True, compute the statistics of the precipitation field
81-
conditionally by excluding the areas where the values are below the
82-
threshold R_thr.
83-
use_precip_mask : bool
84-
If True, set pixels outside precipitation areas to the minimum value of
85-
the observed field.
86-
mask_method : {'obs', 'sprog', 'incremental'}
87-
The precipitation/no precipitation method to use with mask: 'obs' = apply R_thr
88-
to the most recently observed precipitation intensity field, 'sprog' = use the
89-
smoothed forecast field from S-PROG, where the AR(p) model has been applied,
90-
'incremental' = iteratively buffer the mask with a certain rate (currently
91-
it is 1 km/min)
92-
use_probmatching : bool
93-
If True, apply probability matching to the forecast field in order to
94-
preserve the distribution of the most recently observed precipitation
95-
field.
81+
conditionally by excluding pixels where the values are below the threshold
82+
R_thr.
83+
mask_method : {'obs','sprog','incremental',None}
84+
The method to use for masking no precipitation areas in the forecast field.
85+
The masked pixels are set to the minimum value of the observations.
86+
'obs' = apply R_thr to the most recently observed precipitation intensity
87+
field, 'sprog' = use the smoothed forecast field from S-PROG, where the
88+
AR(p) model has been applied, 'incremental' = iteratively buffer the mask
89+
with a certain rate (currently it is 1 km/min), None=no masking.
90+
probmatching_method : {'cdf','mean',None}
91+
Method for matching the statistics of the forecast field with those of
92+
the most recently observed one. Requires that mask_method is not None.
93+
'cdf'=map the forecast CDF to the observed one, 'mean'=adjust only the
94+
mean value of the forecast field, None=no matching applied.
9695
callback : function
9796
Optional function that is called after computation of each time step of
9897
the nowcast. The function takes one argument: a three-dimensional array
@@ -110,19 +109,19 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
110109
all available CPUs. Applicable if dask is enabled.
111110
fft_method : str or tuple
112111
A string or a (function,kwargs) tuple defining the FFT method to use
113-
(see utils.fft.get_method). Defaults to "numpy".
112+
(see utils.fft.get_method). Defaults to 'numpy'.
114113
extrap_kwargs : dict
115-
Optional dictionary that is supplied as keyword arguments to the
116-
extrapolation method.
114+
Optional dictionary containing keyword arguments for the extrapolation
115+
method. See the documentation of pysteps.extrapolation.
117116
filter_kwargs : dict
118-
Optional dictionary that is supplied as keyword arguments to the
119-
filter method.
117+
Optional dictionary containing keyword arguments for the filter method.
118+
See the documentation of pysteps.cascade.bandpass_filters.py.
120119
noise_kwargs : dict
121-
Optional dictionary that is supplied as keyword arguments to the
122-
initializer of the noise generator.
120+
Optional dictionary containing keyword arguments for the initializer of
121+
the noise generator. See the documentation of pysteps.noise.fftgenerators.
123122
vel_pert_kwargs : dict
124-
Optional dictionary that is supplied as keyword arguments to the
125-
initializer of the velocity perturbator.
123+
Optional dictionary containing keyword arguments for the initializer of
124+
the velocity perturbator. See the documentation of pysteps.noise.motion.
126125
127126
Returns
128127
-------
@@ -139,9 +138,9 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
139138
140139
Notes
141140
-----
142-
If noise_method and vel_pert_method are set to None and n_ens_members is set
143-
to 1, the produced nowcast is deterministic (i.e. the S-PROG nowcast, see
144-
:cite:`Seed2003`).
141+
If noise_method and vel_pert_method are None, n_ens_members is 1,
142+
mask_method is 'sprog' and probmatching_method is 'mean', the deterministic
143+
S-PROG nowcast is generated, see :cite:`Seed2003`.
145144
146145
References
147146
----------
@@ -156,14 +155,17 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
156155
if np.any(~np.isfinite(V)):
157156
raise ValueError("V contains non-finite values")
158157

159-
if mask_method not in ["obs", "sprog", "incremental"]:
160-
raise ValueError("unknown mask method %s: must be 'obs', 'sprog' or 'incremental'" % mask_method)
158+
if mask_method not in ["obs", "sprog", "incremental", None]:
159+
raise ValueError("unknown mask method %s: must be 'obs', 'sprog' or 'incremental' or None" % mask_method)
161160

162161
if conditional and R_thr is None:
163162
raise ValueError("conditional=True but R_thr is not set")
164163

165-
if use_probmatching and R_thr is None:
166-
raise ValueError("use_probmatching=True but R_thr is not set")
164+
if probmatching_method is not None and R_thr is None:
165+
raise ValueError("probmatching_method!=None but R_thr is not set")
166+
167+
if probmatching_method is not None and mask_method is None:
168+
raise ValueError("probmatching_method!=None but mask_method=None")
167169

168170
if kmperpixel is None:
169171
if vel_pert_method is not None:
@@ -199,9 +201,8 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
199201
print("noise adjustment: %s" % ("yes" if noise_stddev_adj else "no"))
200202
print("velocity perturbator: %s" % vel_pert_method)
201203
print("conditional statistics: %s" % ("yes" if conditional else "no"))
202-
print("precipitation mask: %s" % ("yes" if use_precip_mask else "no"))
203-
print("mask method: %s" % mask_method)
204-
print("probability matching: %s" % ("yes" if use_probmatching else "no"))
204+
print("precip. mask method: %s" % mask_method)
205+
print("probability matching: %s" % probmatching_method)
205206
print("FFT method: %s" % fft_method)
206207
print("")
207208

@@ -219,14 +220,14 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
219220
print("velocity perturbations, perpendicular: %g,%g,%g" % \
220221
(vp_perp[0], vp_perp[1], vp_perp[2]))
221222

222-
if conditional or use_probmatching:
223-
print("conditional precip. intensity threshold: %g" % R_thr)
223+
if conditional or mask_method is not None:
224+
print("precip. intensity threshold: %g" % R_thr)
224225

225226
M,N = R.shape[1:]
226227
extrap_method = extrapolation.get_method(extrap_method)
227228
R = R[-(ar_order + 1):, :, :].copy()
228229

229-
if conditional or use_probmatching:
230+
if conditional:
230231
MASK_thr = np.logical_and.reduce([R[i, :, :] >= R_thr for i in range(R.shape[0])])
231232
else:
232233
MASK_thr = None
@@ -342,29 +343,32 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
342343
D = [None for j in range(n_ens_members)]
343344
R_f = [[] for j in range(n_ens_members)]
344345

345-
if use_precip_mask:
346+
if mask_method is not None:
347+
MASK_prec = R[-1, :, :] >= R_thr
348+
346349
if mask_method == "obs":
347-
MASK_prec = R[-1, :, :] >= R_thr
350+
pass
348351
# add a slight buffer to the mask
349352
# n=5
350353
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (n,n))
351354
# MASK_prec = MASK_prec.astype('uint8')
352355
# MASK_prec = cv2.dilate(MASK_prec,kernel).astype(bool)
353356
elif mask_method == "sprog":
354357
# compute the wet area ratio and the precipitation mask
355-
MASK_prec = R[-1, :, :] >= R_thr
356358
war = 1.0*np.sum(MASK_prec) / (R.shape[1]*R.shape[2])
357359
R_m = R_c[0, :, :, :].copy()
358360
elif mask_method == "incremental":
359361
# initialize precip mask for each member
360-
MASK_prec_ = R[-1, :, :] >= R_thr
361-
MASK_prec = [MASK_prec_.copy() for j in range(n_ens_members)]
362+
MASK_prec = [MASK_prec.copy() for j in range(n_ens_members)]
362363
# initialize the structuring element
363364
struct = scipy.ndimage.generate_binary_structure(2, 1)
364365
# iterate it to expand it nxn
365366
n = timestep/kmperpixel
366367
struct = scipy.ndimage.iterate_structure(struct, int((n - 1)/2.))
367368

369+
if probmatching_method == "mean":
370+
mu_0 = np.mean(R[-1, :, :][MASK_prec])
371+
368372
R = R[-1, :, :]
369373

370374
print("Starting nowcast computation.")
@@ -375,7 +379,7 @@ def forecast(R, V, n_timesteps, n_ens_members=24, n_cascade_levels=6, R_thr=None
375379
sys.stdout.flush()
376380
starttime = time.time()
377381

378-
if use_precip_mask and mask_method == "sprog":
382+
if mask_method == "sprog":
379383
for i in range(n_cascade_levels):
380384
# use a separate AR(p) model for the non-perturbed forecast,
381385
# from which the mask is obtained
@@ -434,24 +438,29 @@ def worker(j):
434438
# obtained from the AR(p) model(s)
435439
R_c_ = _recompose_cascade(R_c[j, :, :, :], mu, sigma)
436440

437-
if use_precip_mask:
441+
if mask_method is not None:
438442
# apply the precipitation mask to prevent generation of new
439443
# precipitation into areas where it was not originally
440444
# observed
441445
if mask_method == "obs":
442-
R_c_[~MASK_prec] = R_c_.min()
446+
MASK_prec_ = ~MASK_prec
443447
elif mask_method == "incremental":
444-
R_c_[~MASK_prec[j]] = R_c_.min()
448+
MASK_prec_ = ~MASK_prec[j]
445449
elif mask_method == "sprog":
446-
R_c_[MASK_prec] = R_c_.min()
450+
MASK_prec_ = MASK_prec
447451

448-
if use_probmatching:
449-
## adjust the conditional CDF of the forecast (precipitation
450-
## intensity above the threshold R_thr) to match the most
451-
## recently observed precipitation field
452+
R_c_[MASK_prec_] = R_c_.min()
453+
454+
if probmatching_method == "cdf":
455+
# adjust the conditional CDF of the forecast (precipitation
456+
# intensity above the threshold R_thr) to match the most
457+
# recently observed precipitation field
452458
R_c_ = probmatching.nonparam_match_empirical_cdf(R_c_, R)
459+
elif probmatching_method == "mean":
460+
mu_fct = np.mean(R_c_[~MASK_prec_])
461+
R_c_[~MASK_prec_] = R_c_[~MASK_prec_] - mu_fct + mu_0
453462

454-
if use_precip_mask and mask_method == "incremental":
463+
if mask_method == "incremental":
455464
MASK_prec_ = R_c_ >= R_thr
456465
MASK_prec_ = scipy.ndimage.morphology.binary_dilation(MASK_prec_, struct)
457466
MASK_prec[j] = MASK_prec_
@@ -493,10 +502,7 @@ def worker(j):
493502
R_f[j].append(R_f_[j])
494503

495504
if return_output:
496-
if n_ens_members == 1:
497-
return np.stack(R_f[0])
498-
else:
499-
return np.stack([np.stack(R_f[j]) for j in range(n_ens_members)])
505+
return np.stack([np.stack(R_f[j]) for j in range(n_ens_members)])
500506
else:
501507
return None
502508

0 commit comments

Comments
 (0)