ticoi 0.0.1__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.
Potentially problematic release.
This version of ticoi might be problematic. Click here for more details.
- ticoi/__about__.py +1 -0
- ticoi/__init__.py +0 -0
- ticoi/core.py +1500 -0
- ticoi/cube_data_classxr.py +2204 -0
- ticoi/cube_writer.py +741 -0
- ticoi/example.py +81 -0
- ticoi/filtering_functions.py +676 -0
- ticoi/interpolation_functions.py +236 -0
- ticoi/inversion_functions.py +1015 -0
- ticoi/mjd2date.py +31 -0
- ticoi/optimize_coefficient_functions.py +264 -0
- ticoi/pixel_class.py +1830 -0
- ticoi/seasonality_functions.py +209 -0
- ticoi/utils.py +725 -0
- ticoi-0.0.1.dist-info/METADATA +152 -0
- ticoi-0.0.1.dist-info/RECORD +18 -0
- ticoi-0.0.1.dist-info/WHEEL +4 -0
- ticoi-0.0.1.dist-info/licenses/LICENSE +165 -0
ticoi/utils.py
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author : Laurane Charrier, Lei Guo, Nathan Lioret
|
|
3
|
+
Reference:
|
|
4
|
+
Charrier, L., Yan, Y., Koeniguer, E. C., Leinss, S., & Trouvé, E. (2021). Extraction of velocity time series with an optimal temporal sampling from displacement
|
|
5
|
+
observation networks. IEEE Transactions on Geoscience and Remote Sensing.
|
|
6
|
+
Charrier, L., Yan, Y., Colin Koeniguer, E., Mouginot, J., Millan, R., & Trouvé, E. (2022). Fusion of multi-temporal and multi-sensor ice velocity observations.
|
|
7
|
+
ISPRS annals of the photogrammetry, remote sensing and spatial information sciences, 3, 311-318.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import math as m
|
|
12
|
+
import urllib.request
|
|
13
|
+
|
|
14
|
+
import geopandas as gpd
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pandas as pd
|
|
18
|
+
import seaborn as sns
|
|
19
|
+
import sklearn.metrics as sm
|
|
20
|
+
import xarray as xr
|
|
21
|
+
from joblib import Parallel, delayed
|
|
22
|
+
from pyproj import CRS
|
|
23
|
+
from shapely.geometry import Point, Polygon
|
|
24
|
+
|
|
25
|
+
from ticoi.core import interpolation_core, interpolation_to_data, inversion_core
|
|
26
|
+
from ticoi.cube_data_classxr import CubeDataClass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def moving_average_dates(dates: np.ndarray, data: np.ndarray, v_pos: int, save_lines: bool = False) -> np.ndarray:
|
|
30
|
+
"""
|
|
31
|
+
Compute the moving average of the velocities from data between the given dates.
|
|
32
|
+
|
|
33
|
+
:param dates: [np array] --- An array with all the dates included in data, list
|
|
34
|
+
:param data: [np array] --- An array where each line is (date1, date2, other elements) for which a velocity is computed
|
|
35
|
+
:param v_pos: [int] --- Position in data of the considered variable
|
|
36
|
+
:param save_lines: [bool] [default is False] --- If True, save the lines to use
|
|
37
|
+
|
|
38
|
+
:return: [np array] --- Moving average between consecutive dates
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
ini = []
|
|
42
|
+
# data[:, v_pos] = np.ma.array(data[:, v_pos])
|
|
43
|
+
for i_date, date in enumerate(dates[:, 0]):
|
|
44
|
+
i = 0
|
|
45
|
+
moy = []
|
|
46
|
+
|
|
47
|
+
while i < data.shape[0] and dates[i_date, 1] >= data[i, 0]: # If the velocity observation is between two dates
|
|
48
|
+
if dates[i_date, 0] <= data[i, 1]:
|
|
49
|
+
moy.append(data[i, v_pos])
|
|
50
|
+
i += 1
|
|
51
|
+
if len(moy) != 0:
|
|
52
|
+
ini.append(np.nanmean(moy))
|
|
53
|
+
else:
|
|
54
|
+
ini.append(np.nan)
|
|
55
|
+
|
|
56
|
+
interval_output = (dates[0, 1] - dates[0, 0]) / np.timedelta64(1, "D")
|
|
57
|
+
# ini = np.array([(np.ma.mean(ini[i:i + 2])) for i in range(len(ini) - 1)])
|
|
58
|
+
dates_ini = dates[:, 1] - m.ceil(interval_output / 2)
|
|
59
|
+
if save_lines:
|
|
60
|
+
return np.array([[dates_ini[z], ini[z]] for z in range(len(dates_ini))])
|
|
61
|
+
else:
|
|
62
|
+
return np.array([[dates_ini[z], ini[z]] for z in range(len(dates_ini))])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def find_granule_by_point(input_point): # [lon,lat]
|
|
66
|
+
"""
|
|
67
|
+
Takes an input dictionary (a geojson catalog) and a point to represent AOI and returns a list of the s3 urls corresponding to
|
|
68
|
+
zarr datacubes whose footprint covers the AOI.
|
|
69
|
+
Function from https://github.com/e-marshall/itslivetools.git
|
|
70
|
+
|
|
71
|
+
:param input_point: [list | tuple] --- Point to choose the cube to load ([i, j])
|
|
72
|
+
|
|
73
|
+
:return target_granule_urls: [list] --- List of the name of the cubes which cover input_point
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
with urllib.request.urlopen("https://its-live-data.s3.amazonaws.com/datacubes/catalog_v02.json") as url:
|
|
78
|
+
itslive_catalog = json.loads(url.read().decode())
|
|
79
|
+
|
|
80
|
+
target_granule_urls = []
|
|
81
|
+
|
|
82
|
+
point_geom = Point(input_point[0], input_point[1])
|
|
83
|
+
point_gdf = gpd.GeoDataFrame(crs="epsg:4326", geometry=[point_geom])
|
|
84
|
+
for granule in itslive_catalog["features"]:
|
|
85
|
+
bbox_ls = granule["geometry"]["coordinates"][0]
|
|
86
|
+
bbox_geom = Polygon(bbox_ls)
|
|
87
|
+
bbox_gdf = gpd.GeoDataFrame(index=[0], crs="epsg:4326", geometry=[bbox_geom])
|
|
88
|
+
|
|
89
|
+
if bbox_gdf.contains(point_gdf).all():
|
|
90
|
+
target_granule_urls.append(granule["properties"]["zarr_url"])
|
|
91
|
+
|
|
92
|
+
return target_granule_urls
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def points_of_shp_line(shp_file, proj="EPSG:4326", distance=50, nb_points=None, select=None):
|
|
96
|
+
geolns = gpd.read_file(shp_file)
|
|
97
|
+
if geolns.geom_type.describe()["top"] not in ["LineString", "MultiLineString"]:
|
|
98
|
+
raise ValueError("The shp geometries must be a LineString.")
|
|
99
|
+
|
|
100
|
+
# The selection is given in kilometers -> convert it to meters
|
|
101
|
+
if select is not None and (
|
|
102
|
+
select[0] < 2 * distance
|
|
103
|
+
if distance is not None
|
|
104
|
+
else 100 or select[1] < 2 * distance
|
|
105
|
+
if distance is not None
|
|
106
|
+
else 100
|
|
107
|
+
):
|
|
108
|
+
select[0] *= 1000
|
|
109
|
+
select[1] *= 1000
|
|
110
|
+
|
|
111
|
+
if nb_points is not None:
|
|
112
|
+
if isinstance(select, list) and len(select) == 2:
|
|
113
|
+
point_dist = np.linspace(max(0, select[0]), min(int(geolns.geometry.length.values), select[1]), nb_points)
|
|
114
|
+
else:
|
|
115
|
+
point_dist = np.linspace(0, int(geolns.geometry.length.values), nb_points)
|
|
116
|
+
|
|
117
|
+
elif distance is not None:
|
|
118
|
+
if isinstance(select, list) and len(select) == 2:
|
|
119
|
+
point_dist = np.arange(max(0, select[0]), min(int(geolns.geometry.length.values), select[1] + 1), distance)
|
|
120
|
+
else:
|
|
121
|
+
point_dist = np.arange(0, int(geolns.geometry.length.values), distance)
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError("One of 'distance' or 'nb_points' parameters must not be None.")
|
|
125
|
+
|
|
126
|
+
# Projection of the shapeline coordinates into the cube coordinates system
|
|
127
|
+
if proj is not None and CRS(proj) != geolns.crs:
|
|
128
|
+
geolns = geolns.to_crs(CRS(proj))
|
|
129
|
+
|
|
130
|
+
# Retrieve the points from the line (interpolation)
|
|
131
|
+
geopnts = pd.DataFrame(
|
|
132
|
+
{
|
|
133
|
+
"distance": point_dist,
|
|
134
|
+
"geometry": [(geolns.geometry.interpolate(z, normalized=False)).iloc[0] for z in point_dist],
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return geopnts
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def draw_heatmap(
|
|
142
|
+
line_df,
|
|
143
|
+
savepath=None,
|
|
144
|
+
vminmax=[False, False],
|
|
145
|
+
name="heatmap",
|
|
146
|
+
legend="Distance along a longitudinal profile",
|
|
147
|
+
maplabel="Mean of velocity magnitude [m/y]",
|
|
148
|
+
title="",
|
|
149
|
+
cmap="rainbow",
|
|
150
|
+
figsize=(10, 8),
|
|
151
|
+
centered=False,
|
|
152
|
+
x_tick_frequency=10,
|
|
153
|
+
y_tick_frequency=3,
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
Draw an hovmoller diagram (heatmap).
|
|
157
|
+
|
|
158
|
+
:param savepath: str or None, path where to save the figure. If None, the figure is not saved
|
|
159
|
+
:param vminmax: [int, int] or [False, False] : min and max values used for the plot
|
|
160
|
+
:param name: str, name of the figure to save
|
|
161
|
+
:param legend: str, legend of the x-axis
|
|
162
|
+
:param maplabel: str, label of the colormap
|
|
163
|
+
:param title: str, title of the figure
|
|
164
|
+
:param cmap: str, colormap e.g. coolwarm
|
|
165
|
+
:param figsize: (int, int), size of the figure
|
|
166
|
+
:param centered: bool, if True the colormap is centered on 0
|
|
167
|
+
:param x_tick_frequency: tick frequency for x-axis, the legend will be displayed every x_thick_frequency * num meter (e.g. 100 * 10 = 1 km)
|
|
168
|
+
:param y_tick_frequency: tick frequency for y-axis, the legend will be displayed every y_thick_frequency * freq heatmap (e.g. 3 * 1 = 3 months)
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
line_df = line_df.astype(float)
|
|
172
|
+
|
|
173
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
174
|
+
if vminmax == [False, False]: # bound : mean-3std; mean+3 std
|
|
175
|
+
vminmax = [line_df.mean().mean() - 3 * line_df.std().std(), line_df.mean().mean() + 3 * line_df.std().std()]
|
|
176
|
+
vminmax = [line_df.min().min(), line_df.max().max()]
|
|
177
|
+
|
|
178
|
+
if centered: # Center the colormap on 0
|
|
179
|
+
ax = sns.heatmap(
|
|
180
|
+
data=line_df, vmin=vminmax[0], vmax=vminmax[1], cbar_kws={"label": f"\n {maplabel}"}, cmap=cmap, center=0
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
ax = sns.heatmap(
|
|
184
|
+
data=line_df, vmin=vminmax[0], vmax=vminmax[1], cbar_kws={"label": f"\n {maplabel}"}, cmap=cmap
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Display x-ticks
|
|
188
|
+
x_ticks = range(0, len(line_df.columns), x_tick_frequency)
|
|
189
|
+
x_tick_labels = [f"{line_df.columns[i]}" for i in x_ticks]
|
|
190
|
+
ax.set_xticks(x_ticks)
|
|
191
|
+
ax.set_xticklabels(x_tick_labels, fontsize=14)
|
|
192
|
+
|
|
193
|
+
# Display y-ticks
|
|
194
|
+
y_ticks = range(0, len(line_df.index), y_tick_frequency)
|
|
195
|
+
y_tick_labels = [line_df.index[i].strftime("%Y-%m") for i in y_ticks]
|
|
196
|
+
ax.set_yticks(y_ticks)
|
|
197
|
+
ax.set_yticklabels(y_tick_labels, fontsize=14)
|
|
198
|
+
|
|
199
|
+
# Create a black line every year
|
|
200
|
+
y_tick_frequency = 12
|
|
201
|
+
y_ticks = range(0, len(line_df.index), y_tick_frequency)
|
|
202
|
+
for year in y_ticks:
|
|
203
|
+
ax.axhline(year, color="k", linewidth=1)
|
|
204
|
+
colorbar = ax.collections[0].colorbar
|
|
205
|
+
colorbar.ax.tick_params(labelsize=14)
|
|
206
|
+
|
|
207
|
+
ax.figure.axes[-1].yaxis.label.set_size(16)
|
|
208
|
+
ax.set_title(f"{title}", pad=20, fontsize=16)
|
|
209
|
+
ax.set_xlabel(f"{legend}", labelpad=8, fontsize=16)
|
|
210
|
+
ax.set_ylabel("Central date", fontsize=16)
|
|
211
|
+
if savepath is not None:
|
|
212
|
+
fig.savefig(f"{savepath}{name}.png")
|
|
213
|
+
|
|
214
|
+
plt.show()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# %%========================================================================= #
|
|
218
|
+
# OPTIMIZATION FUNCTIONS #
|
|
219
|
+
# =========================================================================%% #
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def RMSE_TICOI_GT(
|
|
223
|
+
data: list,
|
|
224
|
+
mean: list | None,
|
|
225
|
+
dates_range: np.ndarray | None,
|
|
226
|
+
data_gt: pd.DataFrame,
|
|
227
|
+
i: float | int,
|
|
228
|
+
j: float | int,
|
|
229
|
+
coef: int,
|
|
230
|
+
inversion_kwargs: dict,
|
|
231
|
+
interpolation_kwargs: dict,
|
|
232
|
+
method: str = "stable_ground",
|
|
233
|
+
regu: int | str | None = None,
|
|
234
|
+
unit: int = 365,
|
|
235
|
+
visual: bool = False,
|
|
236
|
+
plot_raw: bool = False,
|
|
237
|
+
vminmax: list | None = None,
|
|
238
|
+
savedir: str | None = None,
|
|
239
|
+
):
|
|
240
|
+
"""
|
|
241
|
+
Compute the RMSE between TICOI results with a given coefficient and "ground truth" data.
|
|
242
|
+
|
|
243
|
+
:param data: [list] --- An array where each line is (date1, date2, other elements ) for which a velocity is computed (correspond to the original displacements)
|
|
244
|
+
:param mean: [list | None] --- Apriori on the average
|
|
245
|
+
:param dates_range: [np array | None] --- List of np.datetime64 [D], dates of the estimated displacement in X with an irregular temporal sampling (ILF)
|
|
246
|
+
:param data_gt: [pd dataframe] --- "Ground truth" data to which TICOI results are compared
|
|
247
|
+
:params i, j: [float | int] --- Coordinates of the point in pixel
|
|
248
|
+
:param coef: [int] --- Coef of Tikhonov regularisation
|
|
249
|
+
:param inversion_kwargs: [dict] --- Inversion parameters
|
|
250
|
+
:param interpolation_kwargs: [dict] --- Parameters for the interpolation to GT dates (less parameters than for core interpolation)
|
|
251
|
+
:param method: [str] [default is 'stable_ground'] --- Method to be used to optimise the coef (among 'ground_truth' and 'stable_ground')
|
|
252
|
+
:parma unit: [int] [default is 365] --- 365 if the unit is m/y, 1 if the unit is m/d
|
|
253
|
+
:param visual: [bool] [default is False] --- Plot interpolated and GT velocities
|
|
254
|
+
:param plot_raw: [bool] [default is False] --- Add raw data to the plot
|
|
255
|
+
:param vminmax: [list | None] [default is None] --- Specify the vertical limits of the plot
|
|
256
|
+
:param savedir: [str | None] [default is None] --- Save the figure to this location
|
|
257
|
+
|
|
258
|
+
:return RMSE: Root Mean Square Error between TICOI results interpolated to "ground truth" (GT) dates, and GT data
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
# Proceed to inversion
|
|
262
|
+
if regu is None:
|
|
263
|
+
A, result, dataf = inversion_core(data, i, j, dates_range=dates_range, mean=mean, coef=coef, **inversion_kwargs)
|
|
264
|
+
else:
|
|
265
|
+
A, result, dataf = inversion_core(
|
|
266
|
+
data, i, j, dates_range=dates_range, mean=mean, coef=coef, regu=regu, **inversion_kwargs
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if not visual or not plot_raw:
|
|
270
|
+
del data
|
|
271
|
+
del dates_range, mean
|
|
272
|
+
|
|
273
|
+
# Proceed to interpolation
|
|
274
|
+
if method == "ground_truth":
|
|
275
|
+
dataf_lp = interpolation_to_data(result, data_gt, **interpolation_kwargs)
|
|
276
|
+
del A, result, dataf
|
|
277
|
+
|
|
278
|
+
# RMSE between TICOI result and ground truth data
|
|
279
|
+
RMSE = np.sqrt(sm.mean_squared_error(dataf_lp[["vx", "vy"]], data_gt[["vx", "vy"]]))
|
|
280
|
+
|
|
281
|
+
## Plot the interpolated velocity magnitudes along with GT velocity magnitudes
|
|
282
|
+
if visual:
|
|
283
|
+
data_gt = data_gt.reset_index()
|
|
284
|
+
dataf_lp = dataf_lp.reset_index()
|
|
285
|
+
|
|
286
|
+
# Magnitude of the velocities
|
|
287
|
+
vv_gt = np.sqrt(data_gt["vx"] ** 2 + data_gt["vy"] ** 2) # GT data
|
|
288
|
+
vv_lp = np.sqrt(dataf_lp["vx"] ** 2 + dataf_lp["vy"] ** 2) # TICOI results interpolated to GT data
|
|
289
|
+
# Offsets and central dates are the same as TICOI was interpolated to GT dates
|
|
290
|
+
offset = data_gt["date2"] - data_gt["date1"]
|
|
291
|
+
central_dates = data_gt["date1"] + offset // 2
|
|
292
|
+
|
|
293
|
+
fig, ax = plt.subplots(figsize=(12, 6 / 1.8))
|
|
294
|
+
|
|
295
|
+
# Plot raw data
|
|
296
|
+
if plot_raw:
|
|
297
|
+
data = pd.DataFrame(
|
|
298
|
+
data={
|
|
299
|
+
"date1": data[0][:, 0],
|
|
300
|
+
"date2": data[0][:, 1],
|
|
301
|
+
"vx": data[1][:, 0],
|
|
302
|
+
"vy": data[1][:, 1],
|
|
303
|
+
"errorx": data[1][:, 2],
|
|
304
|
+
"errory": data[1][:, 3],
|
|
305
|
+
"temporal_baseline": data[1][:, 4],
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
offset_raw = data["date2"] - data["date1"]
|
|
309
|
+
central_dates_raw = data["date1"] + offset_raw / 2
|
|
310
|
+
vv_raw = np.sqrt(
|
|
311
|
+
(data["vx"] * unit / data["temporal_baseline"]) ** 2
|
|
312
|
+
+ (data["vy"] * unit / data["temporal_baseline"]) ** 2
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
ax.plot(
|
|
316
|
+
central_dates_raw,
|
|
317
|
+
vv_raw,
|
|
318
|
+
linestyle="",
|
|
319
|
+
color="green",
|
|
320
|
+
zorder=1,
|
|
321
|
+
marker="o",
|
|
322
|
+
lw=0.7,
|
|
323
|
+
markersize=2,
|
|
324
|
+
alpha=0.7,
|
|
325
|
+
label="Central date of velocity observations",
|
|
326
|
+
)
|
|
327
|
+
ax.errorbar(
|
|
328
|
+
central_dates_raw,
|
|
329
|
+
vv_raw,
|
|
330
|
+
xerr=offset_raw / 2,
|
|
331
|
+
color="green",
|
|
332
|
+
alpha=0.2,
|
|
333
|
+
fmt=",",
|
|
334
|
+
zorder=1,
|
|
335
|
+
label="Temporal baseline of velocity observations [days]",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Plot interpolated velocities
|
|
339
|
+
ax.plot(
|
|
340
|
+
central_dates,
|
|
341
|
+
vv_lp,
|
|
342
|
+
linestyle="",
|
|
343
|
+
marker="o",
|
|
344
|
+
markersize=3,
|
|
345
|
+
color="b",
|
|
346
|
+
label="Central date of Interpolated velocities (TICOI results)",
|
|
347
|
+
)
|
|
348
|
+
ax.errorbar(
|
|
349
|
+
central_dates,
|
|
350
|
+
vv_lp,
|
|
351
|
+
xerr=offset / 2,
|
|
352
|
+
color="b",
|
|
353
|
+
alpha=0.2,
|
|
354
|
+
fmt=",",
|
|
355
|
+
zorder=1,
|
|
356
|
+
label="Temporal baseline of interpolated velocities",
|
|
357
|
+
)
|
|
358
|
+
# Plot "ground truth" velocities
|
|
359
|
+
ax.plot(
|
|
360
|
+
central_dates,
|
|
361
|
+
vv_gt,
|
|
362
|
+
linestyle="",
|
|
363
|
+
color="orange",
|
|
364
|
+
zorder=1,
|
|
365
|
+
marker="o",
|
|
366
|
+
lw=0.7,
|
|
367
|
+
markersize=2,
|
|
368
|
+
alpha=0.7,
|
|
369
|
+
label="Central date of velocity observations",
|
|
370
|
+
)
|
|
371
|
+
ax.errorbar(
|
|
372
|
+
central_dates,
|
|
373
|
+
vv_gt,
|
|
374
|
+
xerr=offset / 2,
|
|
375
|
+
color="orange",
|
|
376
|
+
alpha=0.2,
|
|
377
|
+
fmt=",",
|
|
378
|
+
zorder=1,
|
|
379
|
+
label="Temporal baseline of velocity observations [days]",
|
|
380
|
+
)
|
|
381
|
+
ax.set_ylabel("Velocity magnitude [m/y]")
|
|
382
|
+
|
|
383
|
+
if vminmax is None:
|
|
384
|
+
if plot_raw:
|
|
385
|
+
ax.set_ylim(
|
|
386
|
+
0.8 * min(np.nanmin(vv_gt), np.nanmin(vv_raw)), 1.2 * max(np.nanmax(vv_gt), np.nanmax(vv_raw))
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
ax.set_ylim(0.8 * np.nanmin(vv_gt), 1.2 * np.nanmax(vv_gt))
|
|
390
|
+
else:
|
|
391
|
+
ax.set_ylim(vminmax)
|
|
392
|
+
|
|
393
|
+
ax.legend(loc="lower left", bbox_transform=fig.transFigure, fontsize=7, ncol=2)
|
|
394
|
+
fig.suptitle(
|
|
395
|
+
f"Magnitude of the velocities (ground truth and interpolated ILF) for coef={coef}", fontsize=16
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if savedir is not None:
|
|
399
|
+
fig.savefig(f"{savedir}interpol_vv_gt_{coef}.png")
|
|
400
|
+
|
|
401
|
+
plt.show()
|
|
402
|
+
|
|
403
|
+
return RMSE, None
|
|
404
|
+
|
|
405
|
+
elif method == "stable_ground":
|
|
406
|
+
dataf_lp = interpolation_core(result, **interpolation_kwargs)
|
|
407
|
+
del A, result, dataf
|
|
408
|
+
|
|
409
|
+
data_gt = pd.DataFrame(
|
|
410
|
+
data={
|
|
411
|
+
"vx": np.array([0 for _ in range(dataf_lp.shape[0])]),
|
|
412
|
+
"vy": np.array([0 for _ in range(dataf_lp.shape[0])]),
|
|
413
|
+
},
|
|
414
|
+
index=dataf_lp.index,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# RMSE between TICOI result and ground truth data
|
|
418
|
+
RMSE = np.sqrt(sm.mean_squared_error(dataf_lp[["vx", "vy"]], data_gt[["vx", "vy"]]))
|
|
419
|
+
|
|
420
|
+
# TODO Option to visualize the results (if visual)
|
|
421
|
+
|
|
422
|
+
return RMSE, dataf_lp.shape[0]
|
|
423
|
+
|
|
424
|
+
else:
|
|
425
|
+
raise ValueError("Please select 'ground_truth' or 'stable_ground' as method")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def VVC_TICOI(
|
|
429
|
+
data: list,
|
|
430
|
+
mean: list | None,
|
|
431
|
+
dates_range: np.ndarray | None,
|
|
432
|
+
i: float | int,
|
|
433
|
+
j: float | int,
|
|
434
|
+
coef: int,
|
|
435
|
+
inversion_kwargs: dict,
|
|
436
|
+
interpolation_kwargs: dict,
|
|
437
|
+
regu: int | str | None = None,
|
|
438
|
+
):
|
|
439
|
+
"""
|
|
440
|
+
Compute TICOI for one particular coefficient, and compute the VVC
|
|
441
|
+
:param data:
|
|
442
|
+
:param mean:
|
|
443
|
+
:param dates_range:
|
|
444
|
+
:param i:
|
|
445
|
+
:param j:
|
|
446
|
+
:param coef:
|
|
447
|
+
:param inversion_kwargs:
|
|
448
|
+
:param interpolation_kwargs:
|
|
449
|
+
:param regu:
|
|
450
|
+
:return:
|
|
451
|
+
"""
|
|
452
|
+
# Proceed to inversion
|
|
453
|
+
if regu is None:
|
|
454
|
+
A, result, dataf = inversion_core(data, i, j, dates_range=dates_range, mean=mean, coef=coef, **inversion_kwargs)
|
|
455
|
+
else:
|
|
456
|
+
A, result, dataf = inversion_core(
|
|
457
|
+
data, i, j, dates_range=dates_range, mean=mean, coef=coef, regu=regu, **inversion_kwargs
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Interpolation
|
|
461
|
+
dataf_lp = interpolation_core(result, **interpolation_kwargs)
|
|
462
|
+
del A, result, dataf
|
|
463
|
+
|
|
464
|
+
VVC = (
|
|
465
|
+
np.sqrt(
|
|
466
|
+
np.nansum(dataf_lp["vx"] / np.sqrt(dataf_lp["vx"] ** 2 + dataf_lp["vy"] ** 2)) ** 2
|
|
467
|
+
+ np.nansum(dataf_lp["vy"] / np.sqrt(dataf_lp["vx"] ** 2 + dataf_lp["vy"] ** 2)) ** 2
|
|
468
|
+
)
|
|
469
|
+
/ dataf_lp.shape[0]
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
return VVC, dataf_lp.shape[0]
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def optimize_coef(
|
|
476
|
+
cube: CubeDataClass,
|
|
477
|
+
cube_gt: CubeDataClass,
|
|
478
|
+
i: float | int,
|
|
479
|
+
j: float | int,
|
|
480
|
+
obs_filt: xr.Dataset,
|
|
481
|
+
load_pixel_kwargs: dict,
|
|
482
|
+
inversion_kwargs: dict,
|
|
483
|
+
interpolation_kwargs: dict,
|
|
484
|
+
method: str = "vvc",
|
|
485
|
+
regu: dict | None = None,
|
|
486
|
+
flag: xr.DataArray | None = None,
|
|
487
|
+
cmin: int = 10,
|
|
488
|
+
cmax: int = 1000,
|
|
489
|
+
step: int = 10,
|
|
490
|
+
coefs: list | None = None,
|
|
491
|
+
stats: bool = False,
|
|
492
|
+
parallel: bool = False,
|
|
493
|
+
nb_cpu: int = 8,
|
|
494
|
+
**visual_options,
|
|
495
|
+
):
|
|
496
|
+
"""
|
|
497
|
+
Optimization of the regularization coefficient value for the TICOI post-processing method, either by comparing the results
|
|
498
|
+
to a "ground truth" (method='ground_truth') or a zero velocity in stable ground ('stable_ground'), or by computing the
|
|
499
|
+
Velocity Vector Coherence of the results (method = 'vvc').
|
|
500
|
+
|
|
501
|
+
:param cube: [cube_data_class] --- Data cube used to compute TICOI at point (i, j)
|
|
502
|
+
:param cube_gt: [cube_data_class] --- Data cube of "ground truth" velocities
|
|
503
|
+
:params i, j: [float | int] --- Coordinates of the point where we want to optimise the coefficient
|
|
504
|
+
:param obs_filt: [xr dataset] --- Filtered dataset (e.g. rolling mean)
|
|
505
|
+
:param load_pixel_kwargs: [dict] --- Pixel loading parameters
|
|
506
|
+
:param inversion_kwargs: [dict] --- Inversion parameters
|
|
507
|
+
:param interpolation_kwargs: [dict] --- Parameters for the interpolation to GT dates (less parameters than for core interpolation)
|
|
508
|
+
:param method: [str] [default is 'vvc'] --- Method used to optimize the coef ('ground_truth', 'stable_ground' or 'vvc')
|
|
509
|
+
:parma regu: [dict | None] [default is None] --- Must be a dictionary if flags is not None, otherwise the regularisation method must be passed in the kwargs
|
|
510
|
+
:param flags: [xr dataarray | None] [default is None] --- Divide the cube in several areas where the coefficient is optimized independently
|
|
511
|
+
:param cmin: [int] [default is 10] --- If coefs=None, start point of the range of coefs to be tested
|
|
512
|
+
:param cmax: [int] [default is 1000] --- If coefs=None, stop point of the range of coefs to be tested
|
|
513
|
+
:param step: [int] [default is 10] --- If coefs=None, step for the range of coefs to be tested
|
|
514
|
+
:param coefs: [list | None] [default is None] --- To specify the coefficients to be tested, if None, range(cmin, cmax, step) coefs will be tested
|
|
515
|
+
:param stats: [bool] [default is False] --- Compute some statistics on raw data and GT data
|
|
516
|
+
:param parallel: [bool] [default is False] --- Should the computation of the results for different coefficient be done using parallelization ?
|
|
517
|
+
:param nb_cpu: [int] [default is 8] --- If parallel is True, the number of CPUs to use for parallelization
|
|
518
|
+
:param visual_options: Additional options for plotting purposes during the computation of the RMSE for each coef
|
|
519
|
+
|
|
520
|
+
:return: [pd dataframe] --- Dataframe with the studied coefficients ('coefs'), the resulting RMSEs ('RMSEs'), the standard deviation of similar original and GT data ('std'), how many of those data were used to conduct the computation ('nb_data'), their mean temporal baseline ('temporal_baseline') and their mean velocity values for both x and y components ('mean_v')
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
# Load data at pixel
|
|
524
|
+
if flag is not None:
|
|
525
|
+
if "regu" in inversion_kwargs.keys():
|
|
526
|
+
inversion_kwargs.pop("regu")
|
|
527
|
+
data, mean, dates_range, regu, _ = cube.load_pixel(i, j, rolling_mean=obs_filt, **load_pixel_kwargs, flag=flag)
|
|
528
|
+
else:
|
|
529
|
+
data, mean, dates_range = cube.load_pixel(i, j, rolling_mean=obs_filt, **load_pixel_kwargs)
|
|
530
|
+
regu = None
|
|
531
|
+
dataf = pd.DataFrame(
|
|
532
|
+
data={
|
|
533
|
+
"date1": data[0][:, 0],
|
|
534
|
+
"date2": data[0][:, 1],
|
|
535
|
+
"vx": data[1][:, 0],
|
|
536
|
+
"vy": data[1][:, 1],
|
|
537
|
+
"errorx": data[1][:, 2],
|
|
538
|
+
"errory": data[1][:, 3],
|
|
539
|
+
"temporal_baseline": data[1][:, 4],
|
|
540
|
+
}
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Load ground truth pixel and convert to pd dataframe
|
|
544
|
+
data_gt = None
|
|
545
|
+
if method == "ground_truth":
|
|
546
|
+
assert cube_gt is not None, "Please provide ground truth data for method 'ground_truth'"
|
|
547
|
+
|
|
548
|
+
data_gt = cube_gt.load_pixel(i, j, rolling_mean=obs_filt, **load_pixel_kwargs)[0]
|
|
549
|
+
data_gt = pd.DataFrame(
|
|
550
|
+
data={
|
|
551
|
+
"date1": data_gt[0][:, 0],
|
|
552
|
+
"date2": data_gt[0][:, 1],
|
|
553
|
+
"vx": data_gt[1][:, 0],
|
|
554
|
+
"vy": data_gt[1][:, 1],
|
|
555
|
+
"errorx": data_gt[1][:, 2],
|
|
556
|
+
"errory": data_gt[1][:, 3],
|
|
557
|
+
"temporal_baseline": data_gt[1][:, 4],
|
|
558
|
+
}
|
|
559
|
+
)
|
|
560
|
+
data_gt.index = data_gt["date1"] + (data_gt["date2"] - data_gt["date1"]) // 2
|
|
561
|
+
|
|
562
|
+
# Interpolation must be caried out in between the min and max date of the original data
|
|
563
|
+
data_gt = data_gt[(data_gt["date1"] > dataf["date2"].min()) & (data_gt["date2"] < dataf["date2"].max())]
|
|
564
|
+
|
|
565
|
+
# Must have enough data to make an interpolation
|
|
566
|
+
if data_gt.shape[0] == 0 and data[0].shape[0] <= 2:
|
|
567
|
+
if stats:
|
|
568
|
+
return None
|
|
569
|
+
return None
|
|
570
|
+
|
|
571
|
+
# Coefficients to be tested
|
|
572
|
+
if coefs is None:
|
|
573
|
+
coefs = np.arange(cmin, cmax + 1, step) # range of coef
|
|
574
|
+
else:
|
|
575
|
+
coefs = np.array(coefs)
|
|
576
|
+
|
|
577
|
+
if method == "ground_truth" or method == "stable_ground":
|
|
578
|
+
# Compute RMSE for every coefficient
|
|
579
|
+
if parallel:
|
|
580
|
+
measures = Parallel(n_jobs=nb_cpu, verbose=0)(
|
|
581
|
+
delayed(RMSE_TICOI_GT)(
|
|
582
|
+
data,
|
|
583
|
+
mean,
|
|
584
|
+
dates_range,
|
|
585
|
+
data_gt,
|
|
586
|
+
i,
|
|
587
|
+
j,
|
|
588
|
+
coef,
|
|
589
|
+
inversion_kwargs,
|
|
590
|
+
interpolation_kwargs,
|
|
591
|
+
method=method,
|
|
592
|
+
regu=regu,
|
|
593
|
+
**visual_options,
|
|
594
|
+
)
|
|
595
|
+
for coef in coefs
|
|
596
|
+
)
|
|
597
|
+
else:
|
|
598
|
+
measures = [
|
|
599
|
+
RMSE_TICOI_GT(
|
|
600
|
+
data,
|
|
601
|
+
mean,
|
|
602
|
+
dates_range,
|
|
603
|
+
data_gt,
|
|
604
|
+
i,
|
|
605
|
+
j,
|
|
606
|
+
coef,
|
|
607
|
+
inversion_kwargs,
|
|
608
|
+
interpolation_kwargs,
|
|
609
|
+
method=method,
|
|
610
|
+
regu=regu,
|
|
611
|
+
**visual_options,
|
|
612
|
+
)
|
|
613
|
+
for coef in coefs
|
|
614
|
+
]
|
|
615
|
+
|
|
616
|
+
elif method == "vvc":
|
|
617
|
+
if parallel:
|
|
618
|
+
measures = Parallel(n_jobs=nb_cpu, verbose=0)(
|
|
619
|
+
delayed(VVC_TICOI)(
|
|
620
|
+
data, mean, dates_range, i, j, coef, inversion_kwargs, interpolation_kwargs, regu=regu
|
|
621
|
+
)
|
|
622
|
+
for coef in coefs
|
|
623
|
+
)
|
|
624
|
+
else:
|
|
625
|
+
measures = [
|
|
626
|
+
VVC_TICOI(data, mean, dates_range, i, j, coef, inversion_kwargs, interpolation_kwargs, regu=regu)
|
|
627
|
+
for coef in coefs
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
data_gt_shape = measures[0][1]
|
|
631
|
+
measures = [measures[i][0] for i in range(len(coefs))]
|
|
632
|
+
|
|
633
|
+
if stats:
|
|
634
|
+
mean_disp = (dataf["vx"].mean(), dataf["vy"].mean()) # Displacements mean
|
|
635
|
+
directions = np.arctan(dataf["vy"] / dataf["vx"])
|
|
636
|
+
mean_angle_to_median = np.mean(directions - np.median(directions))
|
|
637
|
+
|
|
638
|
+
dataf["vx"] = dataf["vx"] * 365 / dataf["temporal_baseline"]
|
|
639
|
+
dataf["vy"] = dataf["vy"] * 365 / dataf["temporal_baseline"]
|
|
640
|
+
|
|
641
|
+
# Average temporal baseline
|
|
642
|
+
temporal_baseline = dataf["temporal_baseline"].mean()
|
|
643
|
+
|
|
644
|
+
# Mean of similar data (same acquisition dates) of raw and GT data
|
|
645
|
+
mean_raw = (
|
|
646
|
+
dataf.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
647
|
+
.mean()[["vx", "vy"]]
|
|
648
|
+
.mean()
|
|
649
|
+
)
|
|
650
|
+
# Standard deviation of similar data (same acquisition dates) of raw and GT data
|
|
651
|
+
std_raw = (
|
|
652
|
+
dataf.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
653
|
+
.std(ddof=0)[["vx", "vy"]]
|
|
654
|
+
.mean()
|
|
655
|
+
)
|
|
656
|
+
# Standard deviation of raw data
|
|
657
|
+
std_raw_all = dataf[["vx", "vy"]].std(ddof=0)
|
|
658
|
+
|
|
659
|
+
if method == "ground_truth":
|
|
660
|
+
# Average temporal baseline
|
|
661
|
+
temporal_baseline = (dataf["temporal_baseline"].mean(), data_gt["temporal_baseline"].mean())
|
|
662
|
+
# Mean of similar data (same acquisition dates) of raw and GT data
|
|
663
|
+
mean_raw = (
|
|
664
|
+
dataf.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
665
|
+
.mean()[["vx", "vy"]]
|
|
666
|
+
.mean()
|
|
667
|
+
)
|
|
668
|
+
mean_gt = (
|
|
669
|
+
data_gt.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
670
|
+
.mean()[["vx", "vy"]]
|
|
671
|
+
.mean()
|
|
672
|
+
)
|
|
673
|
+
# Standard deviation of similar data (same acquisition dates) of raw and GT data
|
|
674
|
+
std_raw = (
|
|
675
|
+
dataf.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
676
|
+
.std(ddof=0)[["vx", "vy"]]
|
|
677
|
+
.mean()
|
|
678
|
+
)
|
|
679
|
+
std_gt = (
|
|
680
|
+
data_gt.groupby(["date1", "date2"], as_index=False)[["vx", "vy", "errorx", "errory"]]
|
|
681
|
+
.std(ddof=0)[["vx", "vy"]]
|
|
682
|
+
.mean()
|
|
683
|
+
)
|
|
684
|
+
# Standard deviation of raw and GT data
|
|
685
|
+
std_raw_all = dataf[["vx", "vy"]].std(ddof=0)
|
|
686
|
+
std_gt_all = data_gt[["vx", "vy"]].std(ddof=0)
|
|
687
|
+
|
|
688
|
+
return xr.DataArray(
|
|
689
|
+
data=measures,
|
|
690
|
+
attrs={
|
|
691
|
+
"regu": inversion_kwargs["regu"] if flag is None else regu,
|
|
692
|
+
"nb_data": (dataf.shape[0], data_gt.shape[0]),
|
|
693
|
+
"mean_temporal_baseline": temporal_baseline,
|
|
694
|
+
"mean_disp": mean_disp,
|
|
695
|
+
"mean_angle_to_median": mean_angle_to_median,
|
|
696
|
+
"mean_v": (dataf["vx"].mean(), dataf["vy"].mean(), data_gt["vx"].mean(), data_gt["vy"].mean()),
|
|
697
|
+
"mean_v_similar_data": (mean_raw["vx"], mean_raw["vy"], mean_gt["vx"], mean_gt["vy"]),
|
|
698
|
+
"std_v_similar_data": (std_raw["vx"], std_raw["vy"], std_gt["vx"], std_gt["vy"]),
|
|
699
|
+
"std_raw_data": (std_raw_all["vx"], std_raw_all["vy"], std_gt_all["vx"], std_gt_all["vy"]),
|
|
700
|
+
},
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
elif method == "stable_ground" or method == "vvc":
|
|
704
|
+
return xr.DataArray(
|
|
705
|
+
data=measures,
|
|
706
|
+
attrs={
|
|
707
|
+
"regu": inversion_kwargs["regu"] if flag is None else regu,
|
|
708
|
+
"nb_data": (dataf.shape[0], data_gt_shape),
|
|
709
|
+
"mean_temporal_baseline": temporal_baseline,
|
|
710
|
+
"mean_disp": mean_disp,
|
|
711
|
+
"mean_angle_to_median": mean_angle_to_median,
|
|
712
|
+
"mean_v": (dataf["vx"].mean(), dataf["vy"].mean()),
|
|
713
|
+
"mean_v_similar_data": (mean_raw["vx"], mean_raw["vy"]),
|
|
714
|
+
"std_v_similar_data": (std_raw["vx"], std_raw["vy"]),
|
|
715
|
+
"std_raw_data": (std_raw_all["vx"], std_raw_all["vy"]),
|
|
716
|
+
},
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
return xr.DataArray(
|
|
720
|
+
data=measures,
|
|
721
|
+
attrs={
|
|
722
|
+
"regu": inversion_kwargs["regu"] if flag is None else regu,
|
|
723
|
+
"nb_data": (dataf.shape[0], data_gt.shape[0] if data_gt is not None else None),
|
|
724
|
+
},
|
|
725
|
+
)
|