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/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
+ )