Skip to content

Commit 73bf8b2

Browse files
authored
Xarray/fix even more tests (#513)
* fix test_importer_decorator * fixed some more tests * remove test.nc
1 parent 7455efe commit 73bf8b2

File tree

10 files changed

+154
-78
lines changed

10 files changed

+154
-78
lines changed

pysteps/decorators.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def _add_extra_kwrds_to_docstrings(target_func, extra_kwargs_doc_text):
3232
"""
3333
# Clean up indentation from docstrings for the
3434
# docstrings to be merged correctly.
35+
if target_func.__doc__ is None:
36+
return target_func
3537
extra_kwargs_doc = inspect.cleandoc(extra_kwargs_doc_text)
3638
target_func.__doc__ = inspect.cleandoc(target_func.__doc__)
3739

pysteps/downscaling/rainfarm.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
"""
1919

2020
import warnings
21-
21+
import xarray as xr
2222
import numpy as np
2323
from scipy.signal import convolve
2424
from pysteps.utils.spectral import rapsd
2525
from pysteps.utils.dimension import aggregate_fields
26+
from pysteps.xarray_helpers import compute_lat_lon
2627

2728

2829
def _gaussianize(precip):
@@ -210,7 +211,7 @@ def _balanced_spatial_average(array, kernel):
210211

211212

212213
def downscale(
213-
precip,
214+
precip_dataset: xr.Dataset,
214215
ds_factor,
215216
alpha=None,
216217
threshold=None,
@@ -224,8 +225,8 @@ def downscale(
224225
225226
Parameters
226227
----------
227-
precip: array_like
228-
Array of shape (m, n) containing the input field.
228+
precip: xarray.Dataset
229+
Xarray dataset containing the input field.
229230
The input is expected to contain rain rate values.
230231
All values are required to be finite.
231232
alpha: float, optional
@@ -266,6 +267,8 @@ def downscale(
266267
"""
267268

268269
# Validate inputs
270+
precip_var = precip_dataset.attrs["precip_var"]
271+
precip = precip_dataset[precip_var].values.squeeze()
269272
if not np.isfinite(precip).all():
270273
raise ValueError("All values in 'precip' must be finite.")
271274
if not isinstance(ds_factor, int) or ds_factor <= 0:
@@ -297,8 +300,50 @@ def downscale(
297300
noise_field /= noise_field.std()
298301
noise_field = np.exp(noise_field)
299302

303+
y_new = np.arange(
304+
precip_dataset.y.values[0]
305+
- precip_dataset.y.attrs["stepsize"]
306+
+ precip_dataset.y.attrs["stepsize"] / ds_factor,
307+
precip_dataset.y.values[-1] + precip_dataset.y.attrs["stepsize"] / ds_factor,
308+
precip_dataset.y.attrs["stepsize"] / ds_factor,
309+
)
310+
x_new = np.arange(
311+
precip_dataset.x.values[0]
312+
- precip_dataset.y.attrs["stepsize"]
313+
+ precip_dataset.x.attrs["stepsize"] / ds_factor,
314+
precip_dataset.x.values[-1] + precip_dataset.x.attrs["stepsize"] / ds_factor,
315+
precip_dataset.x.attrs["stepsize"] / ds_factor,
316+
)
317+
lat, lon = compute_lat_lon(x_new, y_new, precip_dataset.attrs["projection"])
318+
noise_dataset = xr.Dataset(
319+
data_vars={precip_var: (["time", "y", "x"], [noise_field])},
320+
coords={
321+
"time": (["time"], precip_dataset.time.values, precip_dataset.time.attrs),
322+
"y": (
323+
["y"],
324+
y_new,
325+
{
326+
**precip_dataset.y.attrs,
327+
"stepsize": precip_dataset.y.attrs["stepsize"] / ds_factor,
328+
},
329+
),
330+
"x": (
331+
["x"],
332+
x_new,
333+
{
334+
**precip_dataset.x.attrs,
335+
"stepsize": precip_dataset.x.attrs["stepsize"] / ds_factor,
336+
},
337+
),
338+
"lon": (["y", "x"], lon, precip_dataset.lon.attrs),
339+
"lat": (["y", "x"], lat, precip_dataset.lat.attrs),
340+
},
341+
attrs=precip_dataset.attrs,
342+
)
343+
300344
# Aggregate the noise field to low resolution
301-
noise_lowres = aggregate_fields(noise_field, ds_factor, axis=(0, 1))
345+
noise_lowres_dataset = aggregate_fields(noise_dataset, ds_factor, dim=("y", "x"))
346+
noise_lowres = noise_lowres_dataset[precip_var].values.squeeze()
302347

303348
# Expand input and noise fields to high resolution
304349
precip_expanded = np.kron(precip, np.ones((ds_factor, ds_factor)))
@@ -322,8 +367,11 @@ def downscale(
322367
if threshold is not None:
323368
precip_highres[precip_highres < threshold] = 0
324369

370+
precip_highres_dataset = noise_dataset.copy(deep=True)
371+
precip_highres_dataset[precip_var][:] = [precip_highres]
372+
325373
# Return the downscaled field and optionally the spectral slope alpha
326374
if return_alpha:
327-
return precip_highres, alpha
375+
return precip_highres_dataset, alpha
328376

329-
return precip_highres
377+
return precip_highres_dataset

pysteps/io/exporters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,12 @@ def initialize_forecast_exporter_netcdf(
511511

512512
if metadata["unit"] == "mm/h":
513513
var_name = "precip_intensity"
514-
var_standard_name = None
514+
var_standard_name = "instantaneous_precipitation_rate"
515515
var_long_name = "instantaneous precipitation rate"
516516
var_unit = "mm h-1"
517517
elif metadata["unit"] == "mm":
518518
var_name = "precip_accum"
519-
var_standard_name = None
519+
var_standard_name = "accumulated_precipitation"
520520
var_long_name = "accumulated precipitation"
521521
var_unit = "mm"
522522
elif metadata["unit"] == "dBZ":

pysteps/io/importers.py

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,6 @@ def _get_threshold_value(precip):
352352
return np.nan
353353

354354

355-
@postprocess_import(dtype="float32")
356355
def import_mrms_grib(filename, extent=None, window_size=4, **kwargs):
357356
"""
358357
Importer for NSSL's Multi-Radar/Multi-Sensor System
@@ -407,7 +406,14 @@ def import_mrms_grib(filename, extent=None, window_size=4, **kwargs):
407406
image dimensions.
408407
Default: window_size=4.
409408
410-
{extra_kwargs_doc}
409+
Other Parameters
410+
----------------
411+
dtype: str
412+
Data-type to which the array is cast.
413+
Valid values: "float32", "float64", "single", and "double".
414+
fillna: float or np.nan
415+
Value used to represent the missing data ("No Coverage").
416+
By default, np.nan is used.
411417
412418
Returns
413419
-------
@@ -420,7 +426,32 @@ def import_mrms_grib(filename, extent=None, window_size=4, **kwargs):
420426
metadata: dict
421427
Associated metadata (pixel sizes, map projections, etc.).
422428
"""
429+
dataset = _import_mrms_grib(filename, extent, window_size, **kwargs)
430+
# Create a function with default arguments for aggregate_fields
431+
block_reduce = partial(aggregate_fields, method="mean", trim=True)
432+
433+
if window_size != (1, 1):
434+
# Downscale data
435+
precip_var = dataset.attrs["precip_var"]
436+
# block_reduce does not handle nan values
437+
if "fillna" in kwargs:
438+
no_data_mask = dataset[precip_var].values == kwargs["fillna"]
439+
else:
440+
no_data_mask = np.isnan(dataset[precip_var].values)
441+
dataset[precip_var].data[no_data_mask] = 0
442+
dataset["no_data_mask"] = (("y", "x"), no_data_mask)
443+
dataset = block_reduce(dataset, window_size, dim=("y", "x"))
444+
445+
# Consider that if a single invalid observation is located in the block,
446+
# then mark that value as invalid.
447+
no_data_mask = dataset.no_data_mask.values == 1.0
448+
dataset = dataset.drop_vars("no_data_mask")
449+
450+
return dataset
451+
423452

453+
@postprocess_import(dtype="float32")
454+
def _import_mrms_grib(filename, extent=None, window_size=4, **kwargs):
424455
del kwargs
425456

426457
if not PYGRIB_IMPORTED:
@@ -466,32 +497,6 @@ def import_mrms_grib(filename, extent=None, window_size=4, **kwargs):
466497
precip = grib_msg.values
467498
no_data_mask = precip == -3 # Missing values
468499

469-
# Create a function with default arguments for aggregate_fields
470-
block_reduce = partial(aggregate_fields, method="mean", trim=True)
471-
472-
if window_size != (1, 1):
473-
# Downscale data
474-
lats = block_reduce(lats, window_size[0])
475-
lons = block_reduce(lons, window_size[1])
476-
477-
# Update the limits
478-
ul_lat, lr_lat = (
479-
lats[0],
480-
lats[-1],
481-
) # Lat from North to south!
482-
ul_lon, lr_lon = lons[0], lons[-1]
483-
484-
precip[no_data_mask] = 0 # block_reduce does not handle nan values
485-
precip = block_reduce(precip, window_size, axis=(0, 1))
486-
487-
# Consider that if a single invalid observation is located in the block,
488-
# then mark that value as invalid.
489-
no_data_mask = block_reduce(
490-
no_data_mask.astype("int"),
491-
window_size,
492-
axis=(0, 1),
493-
).astype(bool)
494-
495500
lons, lats = np.meshgrid(lons, lats)
496501
precip[no_data_mask] = np.nan
497502

pysteps/tests/test_downscaling_rainfarm.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88

99

1010
@pytest.fixture(scope="module")
11-
def data():
12-
precip, metadata = get_precipitation_fields(
11+
def dataset():
12+
precip_dataset = get_precipitation_fields(
1313
num_prev_files=0, num_next_files=0, return_raw=False, metadata=True
1414
)
15-
precip = precip.filled()
16-
precip, metadata = square_domain(precip, metadata, "crop")
17-
return precip, metadata
15+
precip_dataset = square_domain(precip_dataset, "crop")
16+
return precip_dataset
1817

1918

2019
rainfarm_arg_names = (
@@ -35,7 +34,7 @@ def data():
3534

3635
@pytest.mark.parametrize(rainfarm_arg_names, rainfarm_arg_values)
3736
def test_rainfarm_shape(
38-
data,
37+
dataset,
3938
alpha,
4039
ds_factor,
4140
threshold,
@@ -44,13 +43,13 @@ def test_rainfarm_shape(
4443
kernel_type,
4544
):
4645
"""Test that the output of rainfarm is consistent with the downscaling factor."""
47-
precip, metadata = data
48-
window = metadata["xpixelsize"] * ds_factor
49-
precip_lr, __ = aggregate_fields_space(precip, metadata, window)
46+
precip_var = dataset.attrs["precip_var"]
47+
window = dataset.x.attrs["stepsize"] * ds_factor
48+
precip_lr_dataset = aggregate_fields_space(dataset, window)
5049

5150
rainfarm = downscaling.get_method("rainfarm")
52-
precip_hr = rainfarm(
53-
precip_lr,
51+
precip_hr_dataset = rainfarm(
52+
precip_lr_dataset,
5453
alpha=alpha,
5554
ds_factor=ds_factor,
5655
threshold=threshold,
@@ -59,9 +58,15 @@ def test_rainfarm_shape(
5958
kernel_type=kernel_type,
6059
)
6160

62-
assert precip_hr.ndim == precip.ndim
63-
assert precip_hr.shape[0] == precip.shape[0]
64-
assert precip_hr.shape[1] == precip.shape[1]
61+
assert precip_hr_dataset[precip_var].values.ndim == dataset[precip_var].values.ndim
62+
assert (
63+
precip_hr_dataset[precip_var].values.shape[0]
64+
== dataset[precip_var].values.shape[0]
65+
)
66+
assert (
67+
precip_hr_dataset[precip_var].values.shape[1]
68+
== dataset[precip_var].values.shape[1]
69+
)
6570

6671

6772
rainfarm_arg_values = [
@@ -74,7 +79,7 @@ def test_rainfarm_shape(
7479

7580
@pytest.mark.parametrize(rainfarm_arg_names, rainfarm_arg_values)
7681
def test_rainfarm_aggregate(
77-
data,
82+
dataset,
7883
alpha,
7984
ds_factor,
8085
threshold,
@@ -83,22 +88,24 @@ def test_rainfarm_aggregate(
8388
kernel_type,
8489
):
8590
"""Test that the output of rainfarm is equal to original when aggregated."""
86-
precip, metadata = data
87-
window = metadata["xpixelsize"] * ds_factor
88-
precip_lr, __ = aggregate_fields_space(precip, metadata, window)
91+
precip_var = dataset.attrs["precip_var"]
92+
window = dataset.x.attrs["stepsize"] * ds_factor
93+
precip_lr_dataset = aggregate_fields_space(dataset, window)
8994

9095
rainfarm = downscaling.get_method("rainfarm")
91-
precip_hr = rainfarm(
92-
precip_lr,
96+
precip_hr_dataset = rainfarm(
97+
precip_lr_dataset,
9398
alpha=alpha,
9499
ds_factor=ds_factor,
95100
threshold=threshold,
96101
return_alpha=return_alpha,
97102
spectral_fusion=spectral_fusion,
98103
kernel_type=kernel_type,
99104
)
100-
precip_low = aggregate_fields(precip_hr, ds_factor, axis=(0, 1))
105+
precip_low_dataset = aggregate_fields(precip_hr_dataset, ds_factor, dim=("y", "x"))
106+
precip_lr = precip_lr_dataset[precip_var].values
101107
precip_lr[precip_lr < threshold] = 0.0
108+
precip_low = precip_low_dataset[precip_var].values
102109

103110
np.testing.assert_array_almost_equal(precip_lr, precip_low)
104111

@@ -108,7 +115,7 @@ def test_rainfarm_aggregate(
108115

109116
@pytest.mark.parametrize(rainfarm_arg_names, rainfarm_arg_values)
110117
def test_rainfarm_alpha(
111-
data,
118+
dataset,
112119
alpha,
113120
ds_factor,
114121
threshold,
@@ -117,13 +124,12 @@ def test_rainfarm_alpha(
117124
kernel_type,
118125
):
119126
"""Test that rainfarm computes and returns alpha."""
120-
precip, metadata = data
121-
window = metadata["xpixelsize"] * ds_factor
122-
precip_lr, __ = aggregate_fields_space(precip, metadata, window)
127+
window = dataset.x.attrs["stepsize"] * ds_factor
128+
precip_lr_dataset = aggregate_fields_space(dataset, window)
123129

124130
rainfarm = downscaling.get_method("rainfarm")
125-
precip_hr = rainfarm(
126-
precip_lr,
131+
precip_hr_dataset = rainfarm(
132+
precip_lr_dataset,
127133
alpha=alpha,
128134
ds_factor=ds_factor,
129135
threshold=threshold,
@@ -132,8 +138,8 @@ def test_rainfarm_alpha(
132138
kernel_type=kernel_type,
133139
)
134140

135-
assert len(precip_hr) == 2
141+
assert len(precip_hr_dataset) == 2
136142
if alpha is None:
137-
assert not precip_hr[1] == alpha
143+
assert not precip_hr_dataset[1] == alpha
138144
else:
139-
assert precip_hr[1] == alpha
145+
assert precip_hr_dataset[1] == alpha

pysteps/tests/test_feature.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ def test_feature(method, max_num_features):
1414
if method == "shitomasi":
1515
pytest.importorskip("cv2")
1616

17-
input_field = get_precipitation_fields(
17+
input_dataset = get_precipitation_fields(
1818
num_prev_files=0,
1919
num_next_files=0,
2020
return_raw=True,
2121
metadata=False,
2222
upscale=None,
2323
source="mch",
2424
)
25+
precip_var = input_dataset.attrs["precip_var"]
26+
input_field = input_dataset[precip_var].values
2527

2628
detector = feature.get_method(method)
2729

0 commit comments

Comments
 (0)