Skip to content

Commit 9bd613f

Browse files
authored
Refactor Optical flow interfaces (#110)
* Add decorator to check the input_images shape in all optical flow functions * Set private LK methods as public * Simplify syntax, improve docstrings * Remove extra_vectors argument in LK * Refactor outlier detection * Refactor declustering * Refactor interpolation routine * Use numpy.empty instead of empty lists * Improve usage of MaskedArray * Add utils.cleansing, utils.images and utils.interpolate modules * Update tests * Update doc
1 parent 23fd0bd commit 9bd613f

File tree

15 files changed

+1402
-915
lines changed

15 files changed

+1402
-915
lines changed

doc/source/pysteps_reference/utils.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ Implementation of miscellaneous utility functions.
77

88
.. automodule:: pysteps.utils.interface
99
.. automodule:: pysteps.utils.arrays
10+
.. automodule:: pysteps.utils.cleansing
1011
.. automodule:: pysteps.utils.conversion
1112
.. automodule:: pysteps.utils.dimension
1213
.. automodule:: pysteps.utils.fft
14+
.. automodule:: pysteps.utils.images
15+
.. automodule:: pysteps.utils.interpolate
1316
.. automodule:: pysteps.utils.spectral
1417
.. automodule:: pysteps.utils.transformation

examples/LK_buffer_mask.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
# -*- coding: utf-8 -*-
12
"""
23
Handling of no-data in Lucas-Kanade
34
===================================
45
56
Areas of missing data in radar images are typically caused by visibility limits
6-
such as beam blockage and the radar coverage itself. These artifacts can mislead
7+
such as beam blockage and the radar coverage itself. These artifacts can mislead
78
the echo tracking algorithms. For instance, precipitation leaving the domain
89
might be erroneously detected as having nearly stationary velocity.
910
10-
This example shows how the Lucas-Kanade algorithm can be tuned to avoid the
11-
erroneous interpretation of velocities near the maximum range of the radars by
12-
buffering the no-data mask in the radar image in order to exclude all vectors
11+
This example shows how the Lucas-Kanade algorithm can be tuned to avoid the
12+
erroneous interpretation of velocities near the maximum range of the radars by
13+
buffering the no-data mask in the radar image in order to exclude all vectors
1314
detected nearby no-data areas.
1415
"""
1516

@@ -71,7 +72,9 @@
7172
mask[~np.isnan(ref_mm)] = np.nan
7273

7374
# Log-transform the data [dBR]
74-
R, metadata = transformation.dB_transform(R, metadata, threshold=0.1, zerovalue=-15.0)
75+
R, metadata = transformation.dB_transform(
76+
R, metadata, threshold=0.1, zerovalue=-15.0
77+
)
7578

7679
# Keep the reference frame in dBR (for plotting purposes)
7780
ref_dbr = R[0].copy()
@@ -109,10 +112,20 @@
109112
R.data[R.mask] = np.nan
110113

111114
# Use default settings (i.e., no buffering of the radar mask)
112-
x, y, u, v = LK_optflow(R, dense=False, buffer_mask=0, quality_level_ST=0.1)
115+
fd_kwargs1 = {"buffer_mask":0}
116+
xy, uv = LK_optflow(R, dense=False, fd_kwargs=fd_kwargs1)
113117
plt.imshow(ref_dbr, cmap=plt.get_cmap("Greys"))
114118
plt.imshow(mask, cmap=colors.ListedColormap(["black"]), alpha=0.5)
115-
plt.quiver(x, y, u, v, color="red", angles="xy", scale_units="xy", scale=0.2)
119+
plt.quiver(
120+
xy[:, 0],
121+
xy[:, 1],
122+
uv[:, 0],
123+
uv[:, 1],
124+
color="red",
125+
angles="xy",
126+
scale_units="xy",
127+
scale=0.2,
128+
)
116129
circle = plt.Circle((620, 245), 100, color="b", clip_on=False, fill=False)
117130
plt.gca().add_artist(circle)
118131
plt.title("buffer_mask = 0 (default)")
@@ -129,13 +142,24 @@
129142
# 'x,y,u,v = LK_optflow(.....)'.
130143

131144
# with buffer
132-
x, y, u, v = LK_optflow(R, dense=False, buffer_mask=20, quality_level_ST=0.2)
145+
buffer = 10
146+
fd_kwargs2 = {"buffer_mask":buffer}
147+
xy, uv = LK_optflow(R, dense=False, fd_kwargs=fd_kwargs2)
133148
plt.imshow(ref_dbr, cmap=plt.get_cmap("Greys"))
134149
plt.imshow(mask, cmap=colors.ListedColormap(["black"]), alpha=0.5)
135-
plt.quiver(x, y, u, v, color="red", angles="xy", scale_units="xy", scale=0.2)
150+
plt.quiver(
151+
xy[:, 0],
152+
xy[:, 1],
153+
uv[:, 0],
154+
uv[:, 1],
155+
color="red",
156+
angles="xy",
157+
scale_units="xy",
158+
scale=0.2,
159+
)
136160
circle = plt.Circle((620, 245), 100, color="b", clip_on=False, fill=False)
137161
plt.gca().add_artist(circle)
138-
plt.title("buffer_mask = 20")
162+
plt.title("buffer_mask = %i" % buffer)
139163
plt.show()
140164

141165
################################################################################
@@ -148,13 +172,13 @@
148172
# the negative bias that is introduced by the the erroneous interpretation of
149173
# velocities near the maximum range of the radars.
150174

151-
UV1 = LK_optflow(R, dense=True, buffer_mask=0, quality_level_ST=0.1)
152-
UV2 = LK_optflow(R, dense=True, buffer_mask=20, quality_level_ST=0.2)
175+
UV1 = LK_optflow(R, dense=True, fd_kwargs=fd_kwargs1)
176+
UV2 = LK_optflow(R, dense=True, fd_kwargs=fd_kwargs2)
153177

154178
V1 = np.sqrt(UV1[0] ** 2 + UV1[1] ** 2)
155179
V2 = np.sqrt(UV2[0] ** 2 + UV2[1] ** 2)
156180

157-
plt.imshow((V1 - V2) / V2, cmap=cm.RdBu_r, vmin=-0.1, vmax=0.1)
181+
plt.imshow((V1 - V2) / V2, cmap=cm.RdBu_r, vmin=-0.5, vmax=0.5)
158182
plt.colorbar(fraction=0.04, pad=0.04)
159183
plt.title("Relative difference in motion speed")
160184
plt.show()
@@ -184,7 +208,13 @@
184208

185209
# Find the veriyfing observations in the archive
186210
fns = io.archive.find_by_date(
187-
date, root_path, path_fmt, fn_pattern, fn_ext, timestep=5, num_next_files=12
211+
date,
212+
root_path,
213+
path_fmt,
214+
fn_pattern,
215+
fn_ext,
216+
timestep=5,
217+
num_next_files=12,
188218
)
189219

190220
# Read and convert the radar composites
@@ -200,8 +230,8 @@
200230
score_2.append(skill(R_f2[i, :, :], R_o[i + 1, :, :])["corr_s"])
201231

202232
x = (np.arange(12) + 1) * 5 # [min]
203-
plt.plot(x, score_1, label="no mask buffer")
204-
plt.plot(x, score_2, label="with mask buffer")
233+
plt.plot(x, score_1, label="buffer_mask = 0")
234+
plt.plot(x, score_2, label="buffer_mask = %i" % buffer)
205235
plt.legend()
206236
plt.xlabel("Lead time [min]")
207237
plt.ylabel("Corr. coeff. []")

pysteps/decorators.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
pysteps.decorators
3+
==================
4+
5+
Decorators used to define reusable building blocks that can change or extend
6+
the behavior of some functions in pysteps.
7+
8+
.. autosummary::
9+
:toctree: ../generated/
10+
11+
check_motion_input_image
12+
"""
13+
from functools import wraps
14+
15+
import numpy as np
16+
17+
18+
def check_input_frames(minimum_input_frames=2,
19+
maximum_input_frames=np.inf,
20+
just_ndim=False):
21+
"""
22+
Check that the input_images used as inputs in the optical-flow
23+
methods has the correct shape (t, x, y ).
24+
"""
25+
26+
def _check_input_frames(motion_method_func):
27+
@wraps(motion_method_func)
28+
def new_function(*args, **kwargs):
29+
"""
30+
Return new function with the checks prepended to the
31+
target motion_method_func function.
32+
"""
33+
34+
input_images = args[0]
35+
if input_images.ndim != 3:
36+
raise ValueError(
37+
"input_images dimension mismatch.\n"
38+
f"input_images.shape: {str(input_images.shape)}\n"
39+
"(t, x, y ) dimensions expected"
40+
)
41+
42+
if not just_ndim:
43+
num_of_frames = input_images.shape[0]
44+
45+
if minimum_input_frames < num_of_frames > maximum_input_frames:
46+
raise ValueError(
47+
f"input_images frames {num_of_frames} mismatch.\n"
48+
f"Minimum frames: {minimum_input_frames}\n"
49+
f"Maximum frames: {maximum_input_frames}\n"
50+
)
51+
52+
return motion_method_func(*args, **kwargs)
53+
54+
return new_function
55+
56+
return _check_input_frames

0 commit comments

Comments
 (0)