Skip to content

Commit 88e35b4

Browse files
authored
Refactor visualization module (#199)
1) Refactor visualization.utils functions - parse_proj4_string: A more robust implementation of this function is implemented in pyproj. The refactored implementation uses a direct call to the pyproj function. - proj4_to_cartopy: Refactor the proj4_to_cartopy function - Use the parse_proj4_string to construct a dictionary with the proj definitions - Simplify the function code by using additional dictionaries to relate the Pyproj and Cartopy parameters. - Add support for additional cartopy keywords defined in the Proj4 definitions. - Add two plotting utility functions to avoid code duplication and to improve the axis handling: - get_basemap_axis: This function safely gets a basemap axis. If the current axis is not a cartopy axis, it creates a basemap. Otherwise, the current axis is returned. - get_geogrid: Returns the x,y grid used in the plots along with additional metadata. - Solves issue #197. 2) Refactor plotting functions - Refactor the plotting functions to use the new plotting utils - Merge `_plot_field_pcolormesh` and `_plot_field` in a single function. - Avoid plotting the no-data mask separately. Instead, plot it in the `_plot_field` function. - Merge `quiver` and `streamplots` functions into a single one called `plot_motion` for avoiding code duplication. The `quiver` and `streamplot` functions are now wrappers for `plot_motion`. - **IMPORTANT** - Rename the `type` keyword in `plot_precip_field` to `ptype` to avoid shadowing the built-in function with the same name. Add the corresponding deprecation warning. 3) Define a unique zorder for each type of plot - Basemap features zorder - ocean: 0 - land: 0 - lakes: 0 - rivers_lake_centerlines: 0 - coastline: 15 - cultural: 15 - reefs: 15 - minor_islands: 15 - precipitation: 10 - quiver: 20 - stream function: 30 - tracks and contours: 40 Co-authored-by: Daniel Nerini
1 parent 4cc21b3 commit 88e35b4

16 files changed

+1058
-908
lines changed

examples/advection_correction.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"""
1818

1919
from datetime import datetime
20-
import matplotlib.pyplot as pl
20+
import matplotlib.pyplot as plt
2121
import numpy as np
2222

2323
from pysteps import io, motion, rcparams
@@ -134,13 +134,13 @@ def advection_correction(R, T=5, t=1):
134134
# correction to account for this spatial shift. The final result is a smoother
135135
# rainfall accumulation map.
136136

137-
pl.figure(figsize=(9, 4))
138-
pl.subplot(121)
137+
plt.figure(figsize=(9, 4))
138+
plt.subplot(121)
139139
plot_precip_field(R.mean(axis=0), title="3-h rainfall accumulation")
140-
pl.subplot(122)
140+
plt.subplot(122)
141141
plot_precip_field(R_ac, title="Same with advection correction")
142-
pl.tight_layout()
143-
pl.show()
142+
plt.tight_layout()
143+
plt.show()
144144

145145
################################################################################
146146
# Reference

examples/plot_cascade_decomposition.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
99
"""
1010

11-
from matplotlib import cm, pyplot
11+
from matplotlib import cm, pyplot as plt
1212
import numpy as np
1313
import os
1414
from pprint import pprint
@@ -40,6 +40,7 @@
4040

4141
# Plot the rainfall field
4242
plot_precip_field(R, geodata=metadata)
43+
plt.show()
4344

4445
# Log-transform the data
4546
R, metadata = transformation.dB_transform(R, metadata, threshold=0.1, zerovalue=-15.0)
@@ -58,14 +59,15 @@
5859

5960
# Plot the power spectrum
6061
M, N = F.shape
61-
fig, ax = pyplot.subplots()
62+
fig, ax = plt.subplots()
6263
im = ax.imshow(
6364
np.log(F ** 2), vmin=4, vmax=24, cmap=cm.jet, extent=(-N / 2, N / 2, -M / 2, M / 2)
6465
)
6566
cb = fig.colorbar(im)
6667
ax.set_xlabel("Wavenumber $k_x$")
6768
ax.set_ylabel("Wavenumber $k_y$")
6869
ax.set_title("Log-power spectrum of R")
70+
plt.show()
6971

7072
###############################################################################
7173
# Cascade decomposition
@@ -81,7 +83,7 @@
8183

8284
# Plot the bandpass filter weights
8385
L = max(N, M)
84-
fig, ax = pyplot.subplots()
86+
fig, ax = plt.subplots()
8587
for k in range(num_cascade_levels):
8688
ax.semilogx(
8789
np.linspace(0, L / 2, len(filter["weights_1d"][k, :])),
@@ -97,6 +99,7 @@
9799
ax.set_xlabel("Radial wavenumber $|\mathbf{k}|$")
98100
ax.set_ylabel("Normalized weight")
99101
ax.set_title("Bandpass filter weights")
102+
plt.show()
100103

101104
###############################################################################
102105
# Finally, apply the 2D Gaussian filters to decompose the radar rainfall field
@@ -110,7 +113,7 @@
110113
sigma = decomp["stds"][i]
111114
decomp["cascade_levels"][i] = (decomp["cascade_levels"][i] - mu) / sigma
112115

113-
fig, ax = pyplot.subplots(nrows=2, ncols=4)
116+
fig, ax = plt.subplots(nrows=2, ncols=4)
114117

115118
ax[0, 0].imshow(R, cmap=cm.RdBu_r, vmin=-5, vmax=5)
116119
ax[0, 1].imshow(decomp["cascade_levels"][0], cmap=cm.RdBu_r, vmin=-3, vmax=3)
@@ -134,6 +137,7 @@
134137
for j in range(4):
135138
ax[i, j].set_xticks([])
136139
ax[i, j].set_yticks([])
137-
pyplot.tight_layout()
140+
plt.tight_layout()
141+
plt.show()
138142

139143
# sphinx_gallery_thumbnail_number = 4

examples/plot_noise_generators.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
field.
1212
"""
1313

14-
from matplotlib import cm, pyplot
14+
from matplotlib import cm, pyplot as plt
1515
import numpy as np
1616
import os
1717
from pprint import pprint
@@ -44,6 +44,7 @@
4444

4545
# Plot the rainfall field
4646
plot_precip_field(R, geodata=metadata)
47+
plt.show()
4748

4849
# Log-transform the data
4950
R, metadata = transformation.dB_transform(R, metadata, threshold=0.1, zerovalue=-15.0)
@@ -79,7 +80,7 @@
7980
b2 = Fp["pars"][3]
8081

8182
# Plot the observed power spectrum and the model
82-
fig, ax = pyplot.subplots()
83+
fig, ax = plt.subplots()
8384
plot_scales = [512, 256, 128, 64, 32, 16, 8, 4]
8485
plot_spectrum1d(
8586
freq,
@@ -101,11 +102,12 @@
101102
label="Fit",
102103
wavelength_ticks=plot_scales,
103104
)
104-
pyplot.legend()
105+
plt.legend()
105106
ax.set_title(
106107
"Radially averaged log-power spectrum of R\n"
107108
r"$\omega_0=%.0f km, \beta_1=%.1f, \beta_2=%.1f$" % (w0, b1, b2)
108109
)
110+
plt.show()
109111

110112
###############################################################################
111113
# Nonparametric filter
@@ -136,7 +138,7 @@
136138

137139
# Plot the generated noise fields
138140

139-
fig, ax = pyplot.subplots(nrows=2, ncols=3)
141+
fig, ax = plt.subplots(nrows=2, ncols=3)
140142

141143
# parametric noise
142144
ax[0, 0].imshow(Np[0], cmap=cm.RdBu_r, vmin=-3, vmax=3)
@@ -152,7 +154,8 @@
152154
for j in range(3):
153155
ax[i, j].set_xticks([])
154156
ax[i, j].set_yticks([])
155-
pyplot.tight_layout()
157+
plt.tight_layout()
158+
plt.show()
156159

157160
###############################################################################
158161
# The above figure highlights the main limitation of the parametric approach

examples/plot_steps_nowcast.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161

6262
# Plot the rainfall field
6363
plot_precip_field(R[-1, :, :], geodata=metadata)
64+
plt.show()
6465

6566
# Log-transform the data to unit of dBR, set the threshold to 0.1 mm/h,
6667
# set the fill value to -15 dBR
@@ -104,6 +105,7 @@
104105
geodata=metadata,
105106
title="S-PROG (+ %i min)" % (n_leadtimes * timestep),
106107
)
108+
plt.show()
107109

108110
###############################################################################
109111
# As we can see from the figure above, the forecast produced by S-PROG is a
@@ -150,6 +152,7 @@
150152
geodata=metadata,
151153
title="Ensemble mean (+ %i min)" % (n_leadtimes * timestep),
152154
)
155+
plt.show()
153156

154157
###############################################################################
155158
# The mean of the ensemble displays similar properties as the S-PROG
@@ -166,6 +169,7 @@
166169
)
167170
ax.set_title("Member %02d" % i)
168171
plt.tight_layout()
172+
plt.show()
169173

170174
###############################################################################
171175
# As we can see from these two members of the ensemble, the stochastic forecast
@@ -183,10 +187,11 @@
183187
plot_precip_field(
184188
P,
185189
geodata=metadata,
186-
type="prob",
190+
ptype="prob",
187191
units="mm/h",
188192
probthr=0.5,
189193
title="Exceedence probability (+ %i min)" % (n_leadtimes * timestep),
190194
)
195+
plt.show()
191196

192197
# sphinx_gallery_thumbnail_number = 5

examples/rainfarm_downscale.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
pprint(metadata)
4747

4848
# Plot the original rainfall field
49-
plt.figure()
5049
plot_precip_field(precip, geodata=metadata)
50+
plt.show()
5151

5252
# Assign the fill value to all the Nans
5353
precip[~np.isfinite(precip)] = metadata["zerovalue"]
@@ -116,7 +116,7 @@
116116
plt.subplots_adjust(wspace=0, hspace=0)
117117

118118
plt.tight_layout()
119-
119+
plt.show()
120120

121121
###############################################################################
122122
# Remarks

pysteps/exceptions.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,3 @@ class DataModelError(Exception):
1919
"""Raised when a file is not compilant with the Data Information Model."""
2020

2121
pass
22-
23-
24-
class UnsupportedSomercProjection(Exception):
25-
"""
26-
Raised when the Swiss Oblique Mercator (somerc) projection is passed
27-
to cartopy.
28-
Necessary since cartopy doesn't support the Swiss projection.
29-
TODO: remove once the somerc projection is supported in cartopy.
30-
"""
31-
32-
pass

pysteps/tests/test_plt_animate.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
5+
import numpy as np
6+
import pytest
7+
8+
from pysteps.tests.helpers import get_precipitation_fields
9+
from pysteps.visualization.animations import animate
10+
11+
12+
def test_animate(tmp_path):
13+
14+
# test data
15+
precip, metadata = get_precipitation_fields(
16+
num_prev_files=2,
17+
num_next_files=0,
18+
return_raw=True,
19+
metadata=True,
20+
upscale=2000,
21+
)
22+
23+
# obs only
24+
animate(precip)
25+
animate(precip, title="title")
26+
animate(precip, timestamps_obs=metadata["timestamps"])
27+
animate(precip, geodata=metadata, map_kwargs={"plot_map": None})
28+
with pytest.raises(ValueError):
29+
animate(precip, timestamps_obs=metadata["timestamps"][:2])
30+
animate(precip, motion_field=np.ones((2, *precip.shape[1:])))
31+
with pytest.raises(ValueError):
32+
animate(precip, motion_plot="test")
33+
34+
# with forecast
35+
animate(precip, precip)
36+
animate(precip, precip, title="title")
37+
animate(precip, precip, timestamps_obs=metadata["timestamps"])
38+
animate(precip, precip, timestamps_obs=metadata["timestamps"], timestep_min=5)
39+
with pytest.raises(ValueError):
40+
animate(precip, precip, ptype="prob")
41+
animate(precip, precip, ptype="prob", prob_thr=1)
42+
animate(precip, precip, ptype="mean")
43+
animate(precip, np.stack((precip, precip)), ptype="ensemble")
44+
45+
# save frames
46+
animate(
47+
precip,
48+
np.stack((precip, precip)),
49+
display_animation=False,
50+
savefig=True,
51+
path_outputs=tmp_path,
52+
fig_dpi=10,
53+
)
54+
assert len(os.listdir(tmp_path)) == 9

pysteps/tests/test_plt_cartopy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_visualization_plot_precip_field(source, map_kwargs, pass_geodata):
3232
if not pass_geodata:
3333
metadata = None
3434

35-
plot_precip_field(field, type="intensity", geodata=metadata, map_kwargs=map_kwargs)
35+
plot_precip_field(field, ptype="intensity", geodata=metadata, map_kwargs=map_kwargs)
3636

3737

3838
if __name__ == "__main__":

pysteps/tests/test_plt_precipfields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_visualization_plot_precip_field(
6060
field_orig = field.copy()
6161
ax = plot_precip_field(
6262
field.copy(),
63-
type=plot_type,
63+
ptype=plot_type,
6464
bbox=bbox,
6565
geodata=None,
6666
colorscale=colorscale,

0 commit comments

Comments
 (0)