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/pixel_class.py
ADDED
|
@@ -0,0 +1,1830 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import matplotlib
|
|
5
|
+
import matplotlib.colors as mcolors
|
|
6
|
+
import matplotlib.lines as malines
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import scipy.fft as fft
|
|
11
|
+
import scipy.signal as signal
|
|
12
|
+
import seaborn as sns
|
|
13
|
+
from scipy.optimize import curve_fit
|
|
14
|
+
from sklearn.metrics import mean_squared_error
|
|
15
|
+
|
|
16
|
+
# %%========================================================================= #
|
|
17
|
+
# DATAFRAME_DATA OBJECT #
|
|
18
|
+
# =========================================================================%% #
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DataframeData:
|
|
22
|
+
"""Object to define a pd.Dataframe storing velocity observations"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, dataf: pd.DataFrame = pd.DataFrame()):
|
|
25
|
+
self.dataf = dataf
|
|
26
|
+
|
|
27
|
+
def set_temporal_baseline_central_date_offset_bar(self):
|
|
28
|
+
"""Set temporal baselines ('temporal_baseline'), centrale date (date_cori), and offset bar ('offset_bar'), used for plotting"""
|
|
29
|
+
|
|
30
|
+
delta = self.dataf["date2"] - self.dataf["date1"] # temporal baseline of the observations
|
|
31
|
+
self.dataf["date_cori"] = np.asarray(self.dataf["date1"] + delta // 2).astype("datetime64[D]") # central date
|
|
32
|
+
try:
|
|
33
|
+
self.dataf["temporal_baseline"] = np.asarray((delta).dt.days).astype(
|
|
34
|
+
"int"
|
|
35
|
+
) # temporal baseline as an integer
|
|
36
|
+
except TypeError:
|
|
37
|
+
self.dataf["temporal_baseline"] = np.array([delta[i].days for i in range(delta.shape[0])])
|
|
38
|
+
self.dataf["offset_bar"] = delta // 2 # to plot the temporal baseline of the plots
|
|
39
|
+
|
|
40
|
+
def set_vx_vy_invert(self, type_data: str = "invert", conversion: int = 365):
|
|
41
|
+
"""
|
|
42
|
+
Convert displacements into velocity
|
|
43
|
+
|
|
44
|
+
:param type_data: [str] [default is "invert"] --- Type of the data to be converted to velocities (generally "invert" or "obs_filt")
|
|
45
|
+
:param conversion: [int] [default is 365] --- Conversion factor: 365 is the unit of the velocity is m/y and 1 if it is m/d
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if "result_dx" in self.dataf.columns:
|
|
49
|
+
self.dataf = self.dataf.rename(columns={"result_dx": "vx", "result_dy": "vy"})
|
|
50
|
+
self.dataf["vx"] = self.dataf["vx"] / self.dataf["temporal_baseline"] * conversion
|
|
51
|
+
self.dataf["vy"] = self.dataf["vy"] / self.dataf["temporal_baseline"] * conversion
|
|
52
|
+
|
|
53
|
+
def set_vx_vy_my(self, type_data: str = "obs_filt", conversion: int = 365):
|
|
54
|
+
if "result_dx" in self.dataf.columns:
|
|
55
|
+
self.dataf = self.dataf.rename(columns={"result_dx": "vx", "result_dy": "vy"})
|
|
56
|
+
self.dataf["vx"] = self.dataf["vx"] * conversion
|
|
57
|
+
self.dataf["vy"] = self.dataf["vy"] * conversion
|
|
58
|
+
|
|
59
|
+
def set_vv(self):
|
|
60
|
+
"""Set velocity magnitude variable (here vv) in the dataframe"""
|
|
61
|
+
|
|
62
|
+
self.dataf["vv"] = np.round(
|
|
63
|
+
np.sqrt((self.dataf["vx"] ** 2 + self.dataf["vy"] ** 2).astype("float")), 2
|
|
64
|
+
) # Compute the magnitude of the velocity
|
|
65
|
+
|
|
66
|
+
def set_minmax(self):
|
|
67
|
+
"""Set the attribute minimum and maximum fir vx, vy, and possibly vv"""
|
|
68
|
+
|
|
69
|
+
self.vxymin = int(self.dataf["vx"].min())
|
|
70
|
+
self.vxymax = int(self.dataf["vx"].max())
|
|
71
|
+
self.vyymin = int(self.dataf["vy"].min())
|
|
72
|
+
self.vyymax = int(self.dataf["vy"].max())
|
|
73
|
+
if "vv" in self.dataf.columns:
|
|
74
|
+
self.vvymin = int(self.dataf["vv"].min())
|
|
75
|
+
self.vvymax = int(self.dataf["vv"].max())
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# %%========================================================================= #
|
|
79
|
+
# PIXEL_CLASS OBJECT #
|
|
80
|
+
# =========================================================================%% #
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class PixelClass:
|
|
84
|
+
"""Object class to store the data on a given pixel"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
save: bool = False,
|
|
89
|
+
path_save: str = "",
|
|
90
|
+
show: bool = True,
|
|
91
|
+
figsize: tuple[int, int] = (10, 6),
|
|
92
|
+
unit: str = "m/y",
|
|
93
|
+
A: np.ndarray | None = None,
|
|
94
|
+
dataobs: pd.DataFrame | None = None,
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Initialize the pixel_class object with general plotting parameters, or set them to default values if no parameters are given.
|
|
98
|
+
|
|
99
|
+
:param save: [bool] [default is False] --- Save the figures to path_save
|
|
100
|
+
:param path_save: [str] [default is ""] --- Path where to save the figures if save is True
|
|
101
|
+
:param show: [bool] [default is True] --- Plot the figures
|
|
102
|
+
:param figsize: [tuple<int, int>] [default is (10, 6)] --- Size of the figures
|
|
103
|
+
:param unit: [str] [default is "m/y"] --- Unit of the velocities ("m/y" or "m/d")
|
|
104
|
+
:param A: [np.array | None] [default is None] --- Design matrix
|
|
105
|
+
:param dataobs: [pd.DataFrame | None] [default is None] --- Observation data
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
self.dataobs = dataobs
|
|
109
|
+
self.datainvert = None
|
|
110
|
+
self.datainterp = None
|
|
111
|
+
self.dataobsfilt = None
|
|
112
|
+
self.save = save
|
|
113
|
+
self.show = show
|
|
114
|
+
self.path_save = path_save
|
|
115
|
+
self.figsize = figsize
|
|
116
|
+
self.unit = unit
|
|
117
|
+
self.A = A
|
|
118
|
+
|
|
119
|
+
def set_data_from_pandas_df(
|
|
120
|
+
self, dataf_ilf: pd.DataFrame, type_data: str = "invert", conversion: int = 365, variables: List[str] = ["vv"]
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Set the data as a pandas DataFrame (using methods from the dataframe_data object).
|
|
124
|
+
|
|
125
|
+
:param dataf_ilf: [pd.DataFrame] --- Data
|
|
126
|
+
:param type_data: [str] [default is "invert"] --- Type of the data (raw data, results of TICO, TICOI...)
|
|
127
|
+
:param conversion: [int] [default is 365] --- Conversion factor: 365 is the unit of the velocity is m/y and 1 if it is m/d
|
|
128
|
+
:param variables: [List<str>] [default is ['vv']] --- List of variable to plot
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
if type_data == "invert":
|
|
132
|
+
self.datainvert = DataframeData(dataf_ilf)
|
|
133
|
+
self.datainvert.dataf = self.datainvert.dataf.rename(columns={"error_x": "errorx", "error_y": "errory"})
|
|
134
|
+
datatemp = self.datainvert
|
|
135
|
+
elif type_data == "interp":
|
|
136
|
+
self.datainterp = DataframeData(dataf_ilf)
|
|
137
|
+
datatemp = self.datainterp
|
|
138
|
+
elif type_data == "obs":
|
|
139
|
+
self.dataobs = DataframeData(dataf_ilf)
|
|
140
|
+
datatemp = self.dataobs
|
|
141
|
+
elif type_data == "obs_filt":
|
|
142
|
+
self.dataobsfilt = DataframeData(dataf_ilf)
|
|
143
|
+
datatemp = self.dataobsfilt
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"Please enter 'invert' for inverted results, 'interp' for ineterpolated results, 'obs' for observation or 'obs_filt' for filtered observations"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
datatemp.set_temporal_baseline_central_date_offset_bar() # Set the temporal baseline,
|
|
150
|
+
if type_data == "invert":
|
|
151
|
+
datatemp.set_vx_vy_invert(type_data=type_data, conversion=conversion) # Convert displacement in vx and vy
|
|
152
|
+
elif type_data == "obs_filt":
|
|
153
|
+
datatemp.set_vx_vy_my(type_data=type_data, conversion=conversion)
|
|
154
|
+
if "vv" in variables:
|
|
155
|
+
datatemp.set_vv() # set velocity magnitude
|
|
156
|
+
datatemp.set_minmax() # set min and max, for figures plots
|
|
157
|
+
|
|
158
|
+
def load(
|
|
159
|
+
self,
|
|
160
|
+
dataf: pd.DataFrame | List[pd.DataFrame],
|
|
161
|
+
type_data: str = "obs",
|
|
162
|
+
dataformat: str = "df",
|
|
163
|
+
save: bool = False,
|
|
164
|
+
show: bool = False,
|
|
165
|
+
figsize: tuple[int, int] = (10, 6),
|
|
166
|
+
unit: str = "m/y",
|
|
167
|
+
path_save: str = "",
|
|
168
|
+
variables: List[str] | None = ["vv", "vx", "vy"],
|
|
169
|
+
A: np.ndarray | None = None,
|
|
170
|
+
):
|
|
171
|
+
"""
|
|
172
|
+
Load the data from dataf and format it in a dataframe_data object using the set_data_from_pandas_df method, depending on the type of data (type_data).
|
|
173
|
+
Initialize the object with general plotting parameters.
|
|
174
|
+
|
|
175
|
+
:param dataf: [pd.DataFrame | List[pd.DataFrame]] --- observations orresults from the inversion
|
|
176
|
+
:param type_data: [str] [default is 'obs'] --- of 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
177
|
+
:param dataformat: [str] [default is 'df'] --- id 'df' dataf is a pd.DataFrame
|
|
178
|
+
:param save: [bool] [default is False] --- if True, save the figures
|
|
179
|
+
:param show: [bool] [default is True] --- if True, show the figures
|
|
180
|
+
:param figsize: tuple[int, int] --- size of the figure
|
|
181
|
+
:param unit: [str] --- unit wanted for plotting
|
|
182
|
+
:param filt: [List[bool] | None] [default is None] --- Are dataf data filtered ? Put True if dataf data are displacemenst, None if all data are not filtered
|
|
183
|
+
:param path_save:[str] --- path where to store the data
|
|
184
|
+
:param variables: [List[str]] [default is ['vv']] --- list of variable to plot
|
|
185
|
+
:param A: [np.array] --- design matrix
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
self.__init__(save=save, show=show, figsize=figsize, unit=unit, path_save=path_save, A=A)
|
|
189
|
+
|
|
190
|
+
conversion = self.get_conversion() # Conversion factor
|
|
191
|
+
if isinstance(dataf, list) and len(dataf) > 1:
|
|
192
|
+
assert isinstance(type_data, list) and (len(dataf) == len(type_data)), (
|
|
193
|
+
"If 'dataf' is a list, 'type_data' must be a list of the same length"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
for i in range(len(dataf)):
|
|
197
|
+
if dataformat == "df":
|
|
198
|
+
self.set_data_from_pandas_df(
|
|
199
|
+
dataf[i], type_data=type_data[i], conversion=conversion, variables=variables
|
|
200
|
+
)
|
|
201
|
+
elif (isinstance(dataf, list) and len(dataf) == 1) or isinstance(dataf, pd.DataFrame):
|
|
202
|
+
assert (isinstance(type_data, list) and len(type_data) == 1) or isinstance(type_data, str), (
|
|
203
|
+
"If 'dataf' is a dataframe or list of a single dataframe, 'type_data' must either be a list of a single string element, or a string"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if dataformat == "df":
|
|
207
|
+
self.set_data_from_pandas_df(
|
|
208
|
+
dataf[0] if isinstance(dataf, list) else dataf,
|
|
209
|
+
type_data=type_data[0] if isinstance(type_data, list) else type_data,
|
|
210
|
+
conversion=conversion,
|
|
211
|
+
variables=variables,
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(f"'dataf' must be a list or a pandas dataframe, not {type(dataf)}")
|
|
215
|
+
|
|
216
|
+
def get_dataf_invert_or_obs_or_interp(self, type_data: str = "obs") -> (pd.DataFrame, str): # type: ignore
|
|
217
|
+
"""
|
|
218
|
+
Get dataframe either obs or invert
|
|
219
|
+
|
|
220
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs', dataf corresponds to obsevations. If 'invert', it corresponds to the inverted velocities
|
|
221
|
+
|
|
222
|
+
:return [pd.DataFrame] --- Dataframe from obs, invert or interp
|
|
223
|
+
:return [str] --- Label used in the legend of the figures
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
# Get data when there is only dataframe loaded
|
|
227
|
+
if self.dataobs is None and self.datainterp is None and self.dataobsfilt is None:
|
|
228
|
+
return self.datainvert, "Results from the inversion"
|
|
229
|
+
elif self.datainvert is None and self.datainterp is None and self.dataobsfilt is None:
|
|
230
|
+
return self.dataobs, "Observations"
|
|
231
|
+
elif self.datainvert is None and self.dataobs is None and self.dataobsfilt is None:
|
|
232
|
+
return self.datainterp, "Results from TICOI"
|
|
233
|
+
elif self.datainvert is None and self.dataobs is None and self.datainter is None:
|
|
234
|
+
return self.dataobsfilt, "Observations filtered"
|
|
235
|
+
elif self.datainvert is None and self.dataobs is None and self.datainterp is None and self.dataobsfilt is None:
|
|
236
|
+
raise ValueError("Please load at least one dataframe")
|
|
237
|
+
else: # else
|
|
238
|
+
if type_data == "invert":
|
|
239
|
+
return self.datainvert, "Results from the inversion"
|
|
240
|
+
elif type_data == "obs":
|
|
241
|
+
return self.dataobs, "Observations"
|
|
242
|
+
elif type_data == "obs_filt":
|
|
243
|
+
return self.dataobsfilt, "Observations filtered"
|
|
244
|
+
else:
|
|
245
|
+
return self.datainterp, "Results from TICOI"
|
|
246
|
+
|
|
247
|
+
def get_conversion(self):
|
|
248
|
+
"""
|
|
249
|
+
Get conversion factor
|
|
250
|
+
|
|
251
|
+
:return: [int] --- conversion factor
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
conversion = 365 if self.unit == "m/y" else 1
|
|
255
|
+
return conversion
|
|
256
|
+
|
|
257
|
+
def get_direction(self, data: "PixelClass.DataframeData") -> (np.array, np.array): # type: ignore
|
|
258
|
+
"""
|
|
259
|
+
Get the direction of the provided data
|
|
260
|
+
|
|
261
|
+
:param data: [ticoi.pixel_class.dataframe_data] --- Dataframe from obs, invert or interp
|
|
262
|
+
|
|
263
|
+
:return directionm: [np.array] --- Directions of the data
|
|
264
|
+
:return directionm_mean: [np.array] --- Averaged direction of the data
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
directionm = np.arctan2(data.dataf["vy"].astype("float32"), data.dataf["vx"].astype("float32"))
|
|
268
|
+
directionm[directionm < 0] += 2 * np.pi
|
|
269
|
+
directionm_mean = np.arctan2(np.mean(data.dataf["vy"]), np.mean(data.dataf["vx"]))
|
|
270
|
+
if directionm_mean < 0:
|
|
271
|
+
directionm_mean += 2 * np.pi
|
|
272
|
+
|
|
273
|
+
# Convert to degrees
|
|
274
|
+
directionm *= 360 / (2 * np.pi)
|
|
275
|
+
directionm_mean *= 360 / (2 * np.pi)
|
|
276
|
+
return directionm, directionm_mean
|
|
277
|
+
|
|
278
|
+
def get_filtered_results(self, filt: str | None = None):
|
|
279
|
+
"""
|
|
280
|
+
Filter TICOI results using a given filter.
|
|
281
|
+
|
|
282
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
283
|
+
|
|
284
|
+
:return vv_filt: [np array] --- Filtered velocities (magnitude)
|
|
285
|
+
:return vv_c: [np array] --- Centered velocities (magnitude)
|
|
286
|
+
:return dates_c: [np array] --- Central dates of the data
|
|
287
|
+
:return dates: [np array] --- For each data, the number of days between its central date and a reference (first date of the data)
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
# Get dates and velocities from TICOI results
|
|
291
|
+
dates_c = (
|
|
292
|
+
self.datainterp.dataf["date1"] + (self.datainterp.dataf["date2"] - self.datainterp.dataf["date1"]) // 2
|
|
293
|
+
) # Central dates
|
|
294
|
+
dates = (
|
|
295
|
+
dates_c - self.datainterp.dataf["date1"].min()
|
|
296
|
+
).dt.days.to_numpy() # Number of days to the reference day (first day of acquisition at the point)
|
|
297
|
+
|
|
298
|
+
vv = self.datainterp.dataf["vv"] # Velocity magnitude
|
|
299
|
+
vv_c = vv - np.mean(vv) # Centered velocities
|
|
300
|
+
|
|
301
|
+
Ts = dates[1] - dates[0]
|
|
302
|
+
|
|
303
|
+
# Filter the results...
|
|
304
|
+
if filt == "highpass": # ...to remove low frequencies (general trend over several years)
|
|
305
|
+
b, a = signal.butter(4, [1 / (1.5 * 365), 1 / (2.001 * Ts)], "bandpass", fs=1 / Ts, output="ba")
|
|
306
|
+
vv_filt = signal.filtfilt(b, a, vv_c)
|
|
307
|
+
elif filt == "lowpass": # ...to ensure Shanon critrion
|
|
308
|
+
sos = signal.butter(4, 1 / (2.001 * Ts), "lowpass", fs=1 / Ts, output="sos")
|
|
309
|
+
vv_filt = signal.sosfilt(sos, vv_c)
|
|
310
|
+
else: # Don't filter
|
|
311
|
+
vv_filt = vv_c
|
|
312
|
+
|
|
313
|
+
return vv_filt, vv_c, dates_c, dates
|
|
314
|
+
|
|
315
|
+
def get_TF(
|
|
316
|
+
self,
|
|
317
|
+
filtered_results: list = None,
|
|
318
|
+
filt: str | None = None,
|
|
319
|
+
verbose: bool = False,
|
|
320
|
+
):
|
|
321
|
+
"""
|
|
322
|
+
Compute the Fourier Transform (TF) of the interpolated results after applying a Hanning window.
|
|
323
|
+
|
|
324
|
+
:param filtered_results: [list | None] [default is None] --- Results of the filtering (get_filtered_results method) if previously processed. If None, it is processed here
|
|
325
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
326
|
+
:param verbose: [bool] [default is False] --- If True, print the maximum and the amplitude of the TF
|
|
327
|
+
|
|
328
|
+
:return vv_tf: [np array] --- TF of the interpolated velocities without windowing
|
|
329
|
+
:return vv_win_tf: [np array] --- TF of the interpolated velocities after windowing
|
|
330
|
+
:return freq: [np array] --- Frequencies of the TF
|
|
331
|
+
:return N: [np array] --- Number of dates
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
if filtered_results is not None:
|
|
335
|
+
vv_filt, vv_c, dates_c, dates = filtered_results
|
|
336
|
+
else:
|
|
337
|
+
vv_filt, vv_c, dates_c, dates = self.get_filtered_results(filt)
|
|
338
|
+
vv_filt = np.array(vv_filt)
|
|
339
|
+
|
|
340
|
+
N = len(dates)
|
|
341
|
+
Ts = dates[1] - dates[0]
|
|
342
|
+
|
|
343
|
+
# Hanning window
|
|
344
|
+
window = signal.windows.hann(N)
|
|
345
|
+
|
|
346
|
+
# TFD
|
|
347
|
+
n = 64 * N
|
|
348
|
+
vv_tf = fft.rfft(vv_filt, n=n)
|
|
349
|
+
vv_win_tf = fft.rfft(vv_filt * window, n=n)
|
|
350
|
+
freq = fft.rfftfreq(n, d=Ts)
|
|
351
|
+
|
|
352
|
+
if verbose:
|
|
353
|
+
f = freq[np.argmax(np.abs(vv_win_tf))]
|
|
354
|
+
print(f"TF maximum for f = {round(f, 5)} day-1 (period of {round(1 / f, 2)} days)")
|
|
355
|
+
print(
|
|
356
|
+
f"Amplitude of the TF at this frequency : {round(2 / N * np.abs(vv_tf[np.argmax(np.abs(vv_win_tf))]), 2)} m/y"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return vv_tf, vv_win_tf, freq, N
|
|
360
|
+
|
|
361
|
+
def get_best_matching_sinus(
|
|
362
|
+
self,
|
|
363
|
+
filt: str | None = None,
|
|
364
|
+
impose_frequency: bool = True,
|
|
365
|
+
raw_seasonality: bool = False,
|
|
366
|
+
several_freq: int = 1,
|
|
367
|
+
verbose: bool = False,
|
|
368
|
+
):
|
|
369
|
+
"""
|
|
370
|
+
Match a sinus (with fixed frequency or not) or a composition of several sinus (fundamental and harmonics) to the resulting TICOI data (and raw data)
|
|
371
|
+
to measure its amplitude, the position of its maximum, the RMSE with the original data...
|
|
372
|
+
|
|
373
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
374
|
+
:param impose_frequency: [bool] [default is True] --- If True, impose the frequency to 1/365.25 days-1 (one year seasonality). If False, look for the best matching frequency too, using the Fourier Transform in the first place
|
|
375
|
+
:param raw_seasonality: [bool] [default is False] --- Also look for the best matching sinus directly on the raw data
|
|
376
|
+
:param several_freq: [int] [default is 1] --- Number of harmonics to be computed (combination of sinus at frequencies 1/365.25, 2/365.25, etc...). If 1, only compute the fundamental.
|
|
377
|
+
:param verbose: [bool] [default is False] --- If True, print the amplitude, the position of the maximum and the RMSE between the best matching sinus and the original data (TICOI results and raw data), and the best matching frequency if impose_frequency is False
|
|
378
|
+
|
|
379
|
+
:return sine_f: [function] --- The function used for the optimization (can be used like sine = sine_f(dates[0], *popt, freqs=several_freq))
|
|
380
|
+
:return popt: [list] --- Parameters of the best matching sinus to TICOI results
|
|
381
|
+
:return popt_raw: [list] --- Parameters of the best matching sinus to raw data
|
|
382
|
+
:return [dates, dates_c, dates_raw]: [list] --- dates, dates_raw: number of days between the central dates and the first central dates, dates_c: central dates of the data
|
|
383
|
+
:return vv_filt: [np array] --- Filtered velocities (magnitude)
|
|
384
|
+
:return stats: [list] --- Statistics about the best matching sinus to TICOI results [first maximum (date), day of the year of the maximum, amplitude, RMSE]
|
|
385
|
+
:return stats_raw: [list] --- Statistics about the best matching sinus to raw data
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
# sine_fconst if impose_frequency else sine_fvar, popt, popt_raw, [dates, dates_c, dates_raw], vv_filt, stats, stats_raw
|
|
389
|
+
|
|
390
|
+
vv_filt, vv_c, dates_c, dates = self.get_filtered_results(filt=filt)
|
|
391
|
+
|
|
392
|
+
N = len(dates)
|
|
393
|
+
|
|
394
|
+
if impose_frequency:
|
|
395
|
+
# Sinus function (can add harmonics)
|
|
396
|
+
def sine_fconst(t, *args, freqs=1, f=1 / 365.25):
|
|
397
|
+
sine = args[0] * np.sin(2 * np.pi * f * t + args[1])
|
|
398
|
+
for freq in range(1, freqs):
|
|
399
|
+
sine += args[2 * freq] * np.sin(2 * np.pi * (freq + 1) * f * t + args[2 * freq + 1])
|
|
400
|
+
return sine + args[-1]
|
|
401
|
+
|
|
402
|
+
f = 1 / 365.25
|
|
403
|
+
|
|
404
|
+
# Find the best matching sinus to TICOI results
|
|
405
|
+
guess = np.concatenate(
|
|
406
|
+
[np.concatenate([[np.max(vv_filt) - np.min(vv_filt), 0] for _ in range(several_freq)]), [0]]
|
|
407
|
+
)
|
|
408
|
+
popt, pcov = curve_fit(lambda t, *args: sine_fconst(t, *args, freqs=several_freq), dates, vv_filt, p0=guess)
|
|
409
|
+
|
|
410
|
+
# Parameters
|
|
411
|
+
sine = sine_fconst(dates, *popt, freqs=several_freq)
|
|
412
|
+
sine_year = sine_fconst(np.linspace(1, 365, 365), *popt, freqs=several_freq)
|
|
413
|
+
|
|
414
|
+
first_max_day = pd.Timedelta(np.argmax(sine_year), "D") + self.datainterp.dataf["date1"].min()
|
|
415
|
+
max_day = first_max_day - pd.Timestamp(year=first_max_day.year, month=1, day=1)
|
|
416
|
+
max_value = np.max(sine_year) - popt[-1]
|
|
417
|
+
RMSE = np.sqrt(mean_squared_error(sine, vv_filt))
|
|
418
|
+
|
|
419
|
+
del sine_year
|
|
420
|
+
|
|
421
|
+
if verbose:
|
|
422
|
+
print(
|
|
423
|
+
f"Amplitude of the best matching sinus (with period 365.25 days) to TICOI results: {round(max_value, 2)} m/y"
|
|
424
|
+
)
|
|
425
|
+
print(f"Maximum at day {max_day.days}")
|
|
426
|
+
print(f"RMSE : {round(RMSE, 2)} m/y")
|
|
427
|
+
|
|
428
|
+
if raw_seasonality:
|
|
429
|
+
# Find the best matching sinus to raw data
|
|
430
|
+
dates_raw = (self.dataobs.dataf.index - self.datainterp.dataf["date1"].min()).days.to_numpy()
|
|
431
|
+
raw_c = self.dataobs.dataf["vv"] - self.dataobs.dataf["vv"].mean()
|
|
432
|
+
guess_raw = np.concatenate(
|
|
433
|
+
[np.concatenate([[np.max(raw_c) - np.min(raw_c), 0] for _ in range(several_freq)]), [0]]
|
|
434
|
+
)
|
|
435
|
+
popt_raw, pcov_raw = curve_fit(
|
|
436
|
+
lambda t, *args: sine_fconst(t, *args, freqs=several_freq), dates_raw, raw_c, p0=guess_raw
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Parameters
|
|
440
|
+
sine_raw = sine_fconst(dates_raw, *popt_raw, freqs=several_freq)
|
|
441
|
+
sine_year_raw = sine_fconst(np.linspace(1, 365, 365), *popt_raw, freqs=several_freq)
|
|
442
|
+
|
|
443
|
+
first_max_day_raw = pd.Timedelta(np.argmax(sine_year_raw), "D") + self.datainterp.dataf["date1"].min()
|
|
444
|
+
max_day_raw = first_max_day_raw - pd.Timestamp(year=first_max_day_raw.year, month=1, day=1)
|
|
445
|
+
max_value_raw = np.max(sine_year_raw) - popt_raw[-1]
|
|
446
|
+
RMSE_raw = np.sqrt(mean_squared_error(sine_raw, raw_c))
|
|
447
|
+
|
|
448
|
+
stats_raw = [first_max_day_raw, max_day_raw, max_value_raw, RMSE_raw]
|
|
449
|
+
del sine_year_raw
|
|
450
|
+
|
|
451
|
+
if verbose:
|
|
452
|
+
print(
|
|
453
|
+
f"Amplitude of the best matching sinus (with period 365.25 days) to raw data: {round(max_value_raw, 2)} m/y"
|
|
454
|
+
)
|
|
455
|
+
print(f"Maximum at day {max_day_raw.days}")
|
|
456
|
+
print(f"RMSE : {round(RMSE_raw, 2)} m/y")
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
vv_tf, vv_win_tf, freq, _ = self.get_TF(vv_filt, vv_c, dates_c, dates, filt=filt, verbose=False)
|
|
460
|
+
|
|
461
|
+
# Sinus function
|
|
462
|
+
def sine_fvar(t, A, f, phi, off, freqs=None):
|
|
463
|
+
return A * np.sin(2 * np.pi * f * t + phi) + off
|
|
464
|
+
|
|
465
|
+
# Initial guess from the TF
|
|
466
|
+
guess = np.array(
|
|
467
|
+
[
|
|
468
|
+
np.max(2 / N * np.abs(vv_win_tf)),
|
|
469
|
+
freq[np.argmax(np.abs(vv_win_tf))],
|
|
470
|
+
np.angle(vv_win_tf)[np.argmax(np.abs(vv_win_tf))],
|
|
471
|
+
np.mean(vv_win_tf),
|
|
472
|
+
],
|
|
473
|
+
dtype="float",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
popt, pcov = curve_fit(sine_fvar, dates, vv_filt, p0=guess)
|
|
477
|
+
A, f, phi, off = popt
|
|
478
|
+
sine = sine_fvar(dates, A, f, phi, off)
|
|
479
|
+
sine_year = sine_fvar(np.linspace(1, 365, 365), A, f, phi, off)
|
|
480
|
+
|
|
481
|
+
first_max_day = pd.Timedelta(np.argmax(sine_year), "D") + self.datainterp.dataf["date1"].min()
|
|
482
|
+
max_day = first_max_day - pd.Timestamp(year=first_max_day.year, month=1, day=1)
|
|
483
|
+
max_value = np.max(sine_year) - off
|
|
484
|
+
RMSE = np.sqrt(mean_squared_error(mean_squared_error(sine, vv_filt)))
|
|
485
|
+
|
|
486
|
+
del sine_year
|
|
487
|
+
|
|
488
|
+
if verbose:
|
|
489
|
+
print(f"Period of the best matching sinus : {round(1 / f, 2)} days")
|
|
490
|
+
print(f"Amplitude : {round(max_value, 2)} m/y")
|
|
491
|
+
print(f"Maximum at day {max_day.days}")
|
|
492
|
+
print(f"RMSE : {round(RMSE, 2)} m/y")
|
|
493
|
+
|
|
494
|
+
stats = [first_max_day, max_day, max_value, RMSE]
|
|
495
|
+
if not (impose_frequency and raw_seasonality):
|
|
496
|
+
popt_raw, dates_raw, stats_raw = None, None, None
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
sine_fconst if impose_frequency else sine_fvar,
|
|
500
|
+
popt,
|
|
501
|
+
popt_raw,
|
|
502
|
+
[dates, dates_c, dates_raw],
|
|
503
|
+
vv_filt,
|
|
504
|
+
stats,
|
|
505
|
+
stats_raw,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# %%========================================================================= #
|
|
509
|
+
# PLOTS ABOUT RAW DATA / INTERPOLATION RESULTS #
|
|
510
|
+
# =========================================================================%% #
|
|
511
|
+
|
|
512
|
+
def plot_vx_vy(self, color: str = "orange", type_data: str = "invert", block_plot: bool = True):
|
|
513
|
+
"""
|
|
514
|
+
Plot vx and vy in two plots of the same figure.
|
|
515
|
+
|
|
516
|
+
:param color: [str] [default is 'orange'] --- Color used for the plot
|
|
517
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs' dataf corresponds to observations, if 'invert', it corresponds to inverted velocity
|
|
518
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
519
|
+
|
|
520
|
+
:return fig, ax: Axis and Figures of the plot
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
524
|
+
|
|
525
|
+
# Display the vx components
|
|
526
|
+
fig, ax = plt.subplots(2, 1, figsize=self.figsize)
|
|
527
|
+
ax[0].set_ylim(data.vxymin, data.vxymax)
|
|
528
|
+
ax[0].plot(data.dataf["date_cori"], data.dataf["vx"], linestyle="", marker="o", markersize=2, color=color)
|
|
529
|
+
ax[0].errorbar(
|
|
530
|
+
data.dataf["date_cori"],
|
|
531
|
+
data.dataf["vx"],
|
|
532
|
+
xerr=data.dataf["offset_bar"],
|
|
533
|
+
color=color,
|
|
534
|
+
alpha=0.2,
|
|
535
|
+
fmt=",",
|
|
536
|
+
zorder=1,
|
|
537
|
+
)
|
|
538
|
+
ax[0].set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
539
|
+
|
|
540
|
+
# Display the vy components
|
|
541
|
+
ax[1].set_ylim(data.vyymin, data.vyymax)
|
|
542
|
+
ax[1].plot(
|
|
543
|
+
data.dataf["date_cori"], data.dataf["vy"], linestyle="", marker="o", markersize=2, color=color, label=label
|
|
544
|
+
)
|
|
545
|
+
ax[1].errorbar(
|
|
546
|
+
data.dataf["date_cori"],
|
|
547
|
+
data.dataf["vy"],
|
|
548
|
+
xerr=data.dataf["offset_bar"],
|
|
549
|
+
color=color,
|
|
550
|
+
alpha=0.2,
|
|
551
|
+
fmt=",",
|
|
552
|
+
zorder=1,
|
|
553
|
+
)
|
|
554
|
+
ax[1].set_ylabel(f"Vy [{self.unit}]", fontsize=14)
|
|
555
|
+
ax[1].set_xlabel("Central dates", fontsize=14)
|
|
556
|
+
plt.subplots_adjust(bottom=0.2)
|
|
557
|
+
ax[1].legend(loc="lower left", bbox_to_anchor=(0.02, -0.4), fontsize=14)
|
|
558
|
+
|
|
559
|
+
fig.suptitle("X and Y components of raw data velocities", y=0.95, fontsize=16)
|
|
560
|
+
|
|
561
|
+
if self.show:
|
|
562
|
+
plt.show(block=block_plot)
|
|
563
|
+
if self.save:
|
|
564
|
+
fig.savefig(f"{self.path_save}/vx_vy_{type_data}.png")
|
|
565
|
+
|
|
566
|
+
return fig, ax
|
|
567
|
+
|
|
568
|
+
def plot_vx_vy_overlaid(
|
|
569
|
+
self,
|
|
570
|
+
colors: List[str] = ["orange", "blue"],
|
|
571
|
+
type_data: str = "invert",
|
|
572
|
+
zoom_on_results: bool = False,
|
|
573
|
+
block_plot: bool = True,
|
|
574
|
+
):
|
|
575
|
+
"""
|
|
576
|
+
Plot vx and vy in two plots of the same figure where inverted/interpolated results overlay the observations (raw data).
|
|
577
|
+
|
|
578
|
+
:param colors: [List[str]] [default is ['orange', 'blue']] --- List of the colors used for the plot (first : raw data, second : overlaying data)
|
|
579
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
580
|
+
:param zoom_on_results: [bool] [default is False] --- If True set the limits of the axis according to the results min and max
|
|
581
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
582
|
+
|
|
583
|
+
:return fig, ax: Axis and Figures of the plot
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
587
|
+
|
|
588
|
+
show = copy.copy(self.show)
|
|
589
|
+
save = copy.copy(self.save)
|
|
590
|
+
self.show, self.save = False, False
|
|
591
|
+
fig, ax = self.plot_vx_vy(color=colors[0], type_data="obs")
|
|
592
|
+
|
|
593
|
+
self.show, self.save = show, save
|
|
594
|
+
|
|
595
|
+
if zoom_on_results:
|
|
596
|
+
ax[0].set_ylim(data.vxymin, data.vxymax)
|
|
597
|
+
ax[0].plot(
|
|
598
|
+
data.dataf["date_cori"], data.dataf["vx"], linestyle="", marker="o", markersize=2, color=colors[1]
|
|
599
|
+
) # Display the vx components
|
|
600
|
+
ax[0].errorbar(
|
|
601
|
+
data.dataf["date_cori"],
|
|
602
|
+
data.dataf["vx"],
|
|
603
|
+
xerr=data.dataf["offset_bar"],
|
|
604
|
+
color=colors[1],
|
|
605
|
+
alpha=0.5,
|
|
606
|
+
fmt=",",
|
|
607
|
+
zorder=1,
|
|
608
|
+
)
|
|
609
|
+
if zoom_on_results:
|
|
610
|
+
ax[1].set_ylim(data.vyymin, data.vyymax)
|
|
611
|
+
ax[1].plot(
|
|
612
|
+
data.dataf["date_cori"],
|
|
613
|
+
data.dataf["vy"],
|
|
614
|
+
linestyle="",
|
|
615
|
+
marker="o",
|
|
616
|
+
markersize=2,
|
|
617
|
+
color=colors[1],
|
|
618
|
+
label=label,
|
|
619
|
+
) # Display the vy components
|
|
620
|
+
ax[1].errorbar(
|
|
621
|
+
data.dataf["date_cori"],
|
|
622
|
+
data.dataf["vy"],
|
|
623
|
+
xerr=data.dataf["offset_bar"],
|
|
624
|
+
color="b",
|
|
625
|
+
alpha=0.2,
|
|
626
|
+
fmt=",",
|
|
627
|
+
zorder=1,
|
|
628
|
+
)
|
|
629
|
+
ax[1].legend(loc="lower left", bbox_to_anchor=(0.0, -0.65), fontsize=14)
|
|
630
|
+
fig.suptitle(
|
|
631
|
+
f"X and Y components of {'interpolated' if type_data == 'interp' else 'inverted'} results, along with raw data",
|
|
632
|
+
y=0.95,
|
|
633
|
+
fontsize=16,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
if self.show:
|
|
637
|
+
plt.show(block=block_plot)
|
|
638
|
+
if self.save:
|
|
639
|
+
if zoom_on_results:
|
|
640
|
+
fig.savefig(f"{self.path_save}/vx_vy_overlaid_zoom_on_results_{type_data}.png")
|
|
641
|
+
else:
|
|
642
|
+
fig.savefig(f"{self.path_save}/vx_vy_overlaid_{type_data}.png")
|
|
643
|
+
|
|
644
|
+
return fig, ax
|
|
645
|
+
|
|
646
|
+
def plot_vv(
|
|
647
|
+
self, color: str = "orange", type_data: str = "invert", block_plot: bool = True, vminmax: list | None = None
|
|
648
|
+
):
|
|
649
|
+
"""
|
|
650
|
+
Plot the velocity magnitude.
|
|
651
|
+
|
|
652
|
+
:param color: [str] [default is 'orange'] --- Color used for the plot
|
|
653
|
+
:param type_data: [str] [default is 'invert'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
654
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
655
|
+
:param vminmax: List[int] [default is None] --- Min and max values for the y-axis of the plots
|
|
656
|
+
|
|
657
|
+
:return fig, ax: Axis and Figure of the plot
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
661
|
+
|
|
662
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
663
|
+
if vminmax is None:
|
|
664
|
+
ax.set_ylim(data.vvymin, data.vvymax)
|
|
665
|
+
else:
|
|
666
|
+
ax.set_ylim(vminmax[0], vminmax[1])
|
|
667
|
+
ax.set_ylabel(f"Velocity magnitude [{self.unit}]", fontsize=14)
|
|
668
|
+
ax.plot(
|
|
669
|
+
data.dataf["date_cori"],
|
|
670
|
+
data.dataf["vv"],
|
|
671
|
+
linestyle="",
|
|
672
|
+
zorder=1,
|
|
673
|
+
marker="o",
|
|
674
|
+
lw=0.7,
|
|
675
|
+
markersize=2,
|
|
676
|
+
color=color,
|
|
677
|
+
label=label,
|
|
678
|
+
)
|
|
679
|
+
ax.errorbar(
|
|
680
|
+
data.dataf["date_cori"],
|
|
681
|
+
data.dataf["vv"],
|
|
682
|
+
xerr=data.dataf["offset_bar"],
|
|
683
|
+
color=color,
|
|
684
|
+
alpha=0.2,
|
|
685
|
+
fmt=",",
|
|
686
|
+
zorder=1,
|
|
687
|
+
)
|
|
688
|
+
plt.subplots_adjust(bottom=0.2)
|
|
689
|
+
ax.legend(loc="lower left", bbox_to_anchor=(0.02, -0.25), fontsize=14)
|
|
690
|
+
ax.set_xlabel("Central dates", fontsize=14)
|
|
691
|
+
|
|
692
|
+
if type_data == "obs":
|
|
693
|
+
fig.suptitle("Magnitude of raw data velocities", y=0.95, fontsize=16)
|
|
694
|
+
elif type_data == "invert":
|
|
695
|
+
fig.suptitle("Magnitude of inverted velocities", y=0.95, fontsize=16)
|
|
696
|
+
elif type_data == "interp":
|
|
697
|
+
fig.suptitle("Magnitude of interpolated velocities", y=0.95, fontsize=16)
|
|
698
|
+
|
|
699
|
+
if self.show:
|
|
700
|
+
plt.show(block=block_plot)
|
|
701
|
+
if self.save:
|
|
702
|
+
fig.savefig(f"{self.path_save}/vv_{type_data}.png")
|
|
703
|
+
|
|
704
|
+
return fig, ax
|
|
705
|
+
|
|
706
|
+
def plot_vv_overlaid(
|
|
707
|
+
self,
|
|
708
|
+
colors: List[str] = ["orange", "blue"],
|
|
709
|
+
type_data: str = "invert",
|
|
710
|
+
zoom_on_results: bool = False,
|
|
711
|
+
block_plot: bool = True,
|
|
712
|
+
vminmax: list | None = None,
|
|
713
|
+
):
|
|
714
|
+
"""
|
|
715
|
+
Plot the velocity magnitude of inverted/interpolated results, overlaying the velocity magnitude of the observations (raw data).
|
|
716
|
+
|
|
717
|
+
:param colors: [List[str]] [default is ['orange', 'blue']] --- List of the colors used for the plot (first : raw data, second : overlaying data)
|
|
718
|
+
:param type_data: [str] [default is 'invert'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
719
|
+
:param zoom_on_results: [bool] [default is False] --- Set the limites of the axis according to the results min and max
|
|
720
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
721
|
+
:param vminmax: List[int] [default is None] --- Min and max values for the y-axis of the plots
|
|
722
|
+
|
|
723
|
+
:return fig, ax: Axis and Figure of the plots
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
727
|
+
|
|
728
|
+
show = copy.copy(self.show)
|
|
729
|
+
save = copy.copy(self.save)
|
|
730
|
+
self.show, self.save = False, False
|
|
731
|
+
fig, ax = self.plot_vv(color=colors[0], type_data="obs", vminmax=vminmax)
|
|
732
|
+
self.show, self.save = show, save
|
|
733
|
+
|
|
734
|
+
if zoom_on_results:
|
|
735
|
+
ax.set_ylim(data.vvymin, data.vvymax)
|
|
736
|
+
ax.plot(
|
|
737
|
+
data.dataf["date_cori"],
|
|
738
|
+
data.dataf["vv"],
|
|
739
|
+
linestyle="",
|
|
740
|
+
zorder=1,
|
|
741
|
+
marker="o",
|
|
742
|
+
lw=0.7,
|
|
743
|
+
markersize=2,
|
|
744
|
+
color=colors[1],
|
|
745
|
+
label="Results from the inversion",
|
|
746
|
+
)
|
|
747
|
+
ax.errorbar(
|
|
748
|
+
data.dataf["date_cori"],
|
|
749
|
+
data.dataf["vv"],
|
|
750
|
+
xerr=data.dataf["offset_bar"],
|
|
751
|
+
color=colors[1],
|
|
752
|
+
alpha=0.2,
|
|
753
|
+
fmt=",",
|
|
754
|
+
zorder=1,
|
|
755
|
+
)
|
|
756
|
+
ax.legend(loc="lower left", bbox_to_anchor=(0, -0.3), fontsize=14)
|
|
757
|
+
fig.suptitle(
|
|
758
|
+
f"Magnitude of {'interpolated' if type_data == 'interp' else 'inverted'} results, along with raw data magnitude",
|
|
759
|
+
y=0.95,
|
|
760
|
+
fontsize=16,
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
if self.show:
|
|
764
|
+
plt.show(block=block_plot)
|
|
765
|
+
if self.save:
|
|
766
|
+
if zoom_on_results:
|
|
767
|
+
fig.savefig(f"{self.path_save}/vv_overlaid_zoom_on_results_{type_data}.png")
|
|
768
|
+
else:
|
|
769
|
+
fig.savefig(f"{self.path_save}/vv_overlaid_{type_data}.png")
|
|
770
|
+
|
|
771
|
+
return fig, ax
|
|
772
|
+
|
|
773
|
+
def plot_vv_quality(self, cmap: str = "viridis", type_data: str = "obs", block_plot: bool = True):
|
|
774
|
+
"""
|
|
775
|
+
Plot error on top of velocity vx and vy.
|
|
776
|
+
|
|
777
|
+
:param cmap: [str] [default is 'viridis''] --- Color map used to mark the errors in the plots
|
|
778
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
779
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting
|
|
780
|
+
:param vminmax: List[int] [default is None] --- Min and max values for the y-axis of the plots
|
|
781
|
+
|
|
782
|
+
:return fig, ax: Axis and Figure of the plots
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
assert "errorx" in self.dataobs.dataf.columns and "errory" in self.dataobs.dataf.columns, (
|
|
786
|
+
"'errorx' and/or 'errory' values are missing in the data, impossible to plot the errors"
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
790
|
+
|
|
791
|
+
qualityx = data.dataf["errorx"]
|
|
792
|
+
qualityy = data.dataf["errory"]
|
|
793
|
+
qualityv = np.sqrt(
|
|
794
|
+
(qualityx / data.dataf["vx"] * qualityx) ** 2 + (qualityy / data.dataf["vy"] * qualityy) ** 2
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
798
|
+
# First subplot
|
|
799
|
+
ax.set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
800
|
+
scat = ax.scatter(data.dataf["date_cori"], data.dataf["vv"], c=qualityv, s=5, cmap=cmap)
|
|
801
|
+
cbar = fig.colorbar(scat, ax=ax, orientation="horizontal", pad=0.2) # Increased pad for spacing
|
|
802
|
+
cbar.set_label("Errors [m/y]", fontsize=14)
|
|
803
|
+
# Adjustments
|
|
804
|
+
plt.subplots_adjust(hspace=0.5, bottom=0.3) # Increase hspace and bottom padding
|
|
805
|
+
fig.suptitle("Error associated to the velocity data", y=0.98, fontsize=16) # Adjusted title position
|
|
806
|
+
# Use tight layout
|
|
807
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
|
808
|
+
if self.show:
|
|
809
|
+
plt.show(block=block_plot)
|
|
810
|
+
if self.save:
|
|
811
|
+
fig.savefig(f"{self.path_save}/vxvy_quality_bas_{type_data}.png")
|
|
812
|
+
|
|
813
|
+
return fig, ax
|
|
814
|
+
|
|
815
|
+
def plot_vx_vy_quality(self, cmap: str = "viridis", type_data: str = "obs", block_plot: bool = True):
|
|
816
|
+
"""
|
|
817
|
+
Plot error on top of velocity magnitude vv
|
|
818
|
+
|
|
819
|
+
:param cmap: [str] [default is 'viridis''] --- Color map used to mark the errors in the plots
|
|
820
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
821
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting
|
|
822
|
+
|
|
823
|
+
:return fig, ax: Axis and Figure of the plots
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
assert "errorx" in self.dataobs.dataf.columns and "errory" in self.dataobs.dataf.columns, (
|
|
827
|
+
"'errorx' and/or 'errory' values are missing in the data, impossible to plot the errors"
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
831
|
+
|
|
832
|
+
qualityx = data.dataf["errorx"]
|
|
833
|
+
qualityy = data.dataf["errory"]
|
|
834
|
+
|
|
835
|
+
fig, ax = plt.subplots(2, 1, figsize=self.figsize)
|
|
836
|
+
# First subplot
|
|
837
|
+
ax[0].set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
838
|
+
scat = ax[0].scatter(data.dataf["date_cori"], data.dataf["vx"], c=qualityx, s=5, cmap=cmap)
|
|
839
|
+
cbar = fig.colorbar(scat, ax=ax[0], orientation="horizontal", pad=0.2) # Increased pad for spacing
|
|
840
|
+
cbar.set_label("Errors [m/y]", fontsize=14)
|
|
841
|
+
|
|
842
|
+
# Second subplot
|
|
843
|
+
ax[1].set_ylabel(f"Vy [{self.unit}]", fontsize=14)
|
|
844
|
+
scat = ax[1].scatter(data.dataf["date_cori"], data.dataf["vy"], c=qualityy, s=5, cmap=cmap)
|
|
845
|
+
cbar = fig.colorbar(scat, ax=ax[1], orientation="horizontal", pad=0.2) # Increased pad for spacing
|
|
846
|
+
cbar.set_label("Errors [m/y]", fontsize=14)
|
|
847
|
+
|
|
848
|
+
# Adjustments
|
|
849
|
+
plt.subplots_adjust(hspace=0.5, bottom=0.3) # Increase hspace and bottom padding
|
|
850
|
+
fig.suptitle("Error associated to the velocity data", y=0.98, fontsize=16) # Adjusted title position
|
|
851
|
+
|
|
852
|
+
# Use tight layout
|
|
853
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
|
854
|
+
|
|
855
|
+
if self.show:
|
|
856
|
+
plt.show(block=block_plot)
|
|
857
|
+
if self.save:
|
|
858
|
+
fig.savefig(f"{self.path_save}/vxvy_quality_bas_{type_data}.png")
|
|
859
|
+
|
|
860
|
+
return fig, ax
|
|
861
|
+
|
|
862
|
+
def plot_direction(
|
|
863
|
+
self, color: str = "orange", type_data: str = "obs", block_plot: bool = True, plot_mean: bool = True
|
|
864
|
+
):
|
|
865
|
+
"""
|
|
866
|
+
Plot the direction of the velocities for each of the data at this point.
|
|
867
|
+
|
|
868
|
+
:param color: [str] [default is 'orange'] --- Color used for the plot
|
|
869
|
+
:param type_data: [str] [default is 'obs'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
870
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
871
|
+
:param plot_mean: [bool] [default is True] --- If True, plot the mean velocity direction
|
|
872
|
+
:return fig, ax: Axis and Figure of the plot
|
|
873
|
+
"""
|
|
874
|
+
|
|
875
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
876
|
+
|
|
877
|
+
directionm, directionm_mean = self.get_direction(data)
|
|
878
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
879
|
+
ax.plot(data.dataf["date_cori"], directionm, linestyle="", marker="o", markersize=2, color=color, label=label)
|
|
880
|
+
if plot_mean:
|
|
881
|
+
ax.hlines(
|
|
882
|
+
directionm_mean,
|
|
883
|
+
np.min(data.dataf["date_cori"]),
|
|
884
|
+
np.max(data.dataf["date_cori"]),
|
|
885
|
+
label=f"Mean direction of {label}",
|
|
886
|
+
)
|
|
887
|
+
ax.set_ylim(0, 360)
|
|
888
|
+
ax.set_ylabel("Direction [°]")
|
|
889
|
+
ax.set_xlabel("Central Dates")
|
|
890
|
+
plt.subplots_adjust(bottom=0.25)
|
|
891
|
+
ax.legend(loc="lower left", bbox_to_anchor=(0, -0.4), ncol=2, fontsize=14)
|
|
892
|
+
fig.suptitle("Direction of the observations", y=0.95, fontsize=16)
|
|
893
|
+
|
|
894
|
+
if self.show:
|
|
895
|
+
plt.show(block=block_plot)
|
|
896
|
+
if self.save:
|
|
897
|
+
fig.savefig(f"{self.path_save}/direction_{type_data}.png")
|
|
898
|
+
|
|
899
|
+
return fig, ax
|
|
900
|
+
|
|
901
|
+
def plot_direction_overlaid(
|
|
902
|
+
self,
|
|
903
|
+
colors: List[str] = ["orange", "blue"],
|
|
904
|
+
type_data: str = "interp",
|
|
905
|
+
block_plot: bool = True,
|
|
906
|
+
plot_mean: bool = True,
|
|
907
|
+
):
|
|
908
|
+
"""
|
|
909
|
+
Plot the velocity direction of inverted/interpolated results, overlaying the velocity direction of the observations (raw data).
|
|
910
|
+
|
|
911
|
+
:param colors: [List[str]] [default is ['orange', 'blue']] --- List of the colors used for the plot (first : raw data, second : overlaying data)
|
|
912
|
+
:param type_data: [str] [default is 'invert'] --- If 'obs' dataf corresponds to obsevations, if 'invert', it corresponds to inverted velocity
|
|
913
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
914
|
+
:param plot_mean: [bool] [default is True] --- If True, plot the mean velocity direction
|
|
915
|
+
|
|
916
|
+
:return fig, ax: Axis and Figure of the plot
|
|
917
|
+
"""
|
|
918
|
+
|
|
919
|
+
data, label = self.get_dataf_invert_or_obs_or_interp(type_data)
|
|
920
|
+
|
|
921
|
+
show = copy.copy(self.show)
|
|
922
|
+
save = copy.copy(self.save)
|
|
923
|
+
self.show, self.save = False, False
|
|
924
|
+
fig, ax = self.plot_direction(color=colors[0], type_data="obs", plot_mean=plot_mean)
|
|
925
|
+
self.show, self.save = show, save
|
|
926
|
+
|
|
927
|
+
directionm, directionm_mean = self.get_direction(data)
|
|
928
|
+
|
|
929
|
+
ax.plot(
|
|
930
|
+
data.dataf["date_cori"], directionm, linestyle="", marker="o", markersize=2, color=colors[1], label=label
|
|
931
|
+
)
|
|
932
|
+
ax.set_ylim(0, 360)
|
|
933
|
+
ax.set_ylabel("Direction [°]", fontsize=14)
|
|
934
|
+
ax.set_xlabel("Central Dates", fontsize=14)
|
|
935
|
+
ax.legend(loc="lower left", bbox_to_anchor=(0, -0.4), ncol=2, fontsize=14)
|
|
936
|
+
fig.suptitle(
|
|
937
|
+
f"Direction of the {'interpolated' if type_data == 'interp' else 'inverted'} results, along with raw data direction",
|
|
938
|
+
y=0.95,
|
|
939
|
+
fontsize=16,
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
if self.show:
|
|
943
|
+
plt.show(block=block_plot)
|
|
944
|
+
if self.save:
|
|
945
|
+
fig.savefig(f"{self.path_save}/direction_overlaid_{type_data}.png")
|
|
946
|
+
|
|
947
|
+
return fig, ax
|
|
948
|
+
|
|
949
|
+
return fig, ax
|
|
950
|
+
|
|
951
|
+
def plot_quality_metrics(self, color: str = "orange"):
|
|
952
|
+
"""
|
|
953
|
+
Plot quality metrics on top of velocity magnitude. It can be the number of observations used for each estimation, and/or the confidence intervals.
|
|
954
|
+
:param color: [str] [default is 'orange'] --- Color used for the plot
|
|
955
|
+
:return:
|
|
956
|
+
"""
|
|
957
|
+
|
|
958
|
+
dataf, label = self.get_dataf_invert_or_obs_or_interp(type_data="interp")
|
|
959
|
+
data = dataf.dataf.dropna(subset=["vx", "vy"]) # drop rows where with no velocity values
|
|
960
|
+
|
|
961
|
+
assert "error_x" and "x_count" not in data.columns, (
|
|
962
|
+
"No quality metrics to display, please re run ticoi using the options Error_propagation or X_contribution"
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
if "error_x" in data.columns:
|
|
966
|
+
data["error_x"] = np.sqrt(data["error_x"])
|
|
967
|
+
data["error_y"] = np.sqrt(data["error_y"])
|
|
968
|
+
data["error_v"] = np.sqrt(
|
|
969
|
+
(data["vx"] / data["vv"] * data["error_x"]) ** 2 + (data["vy"] / data["vv"] * data["error_y"]) ** 2
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
data["confidence_x"] = data["sigma0"].iloc[2] * data["error_x"]
|
|
973
|
+
data["confidence_y"] = data["sigma0"].iloc[3] * data["error_y"]
|
|
974
|
+
data["confidence_v"] = np.nanmean(data["sigma0"].iloc[2:4]) * data["error_v"]
|
|
975
|
+
|
|
976
|
+
if "xcount_x" in data.columns:
|
|
977
|
+
xcount_mean = np.nanmean([data["xcount_x"], data["xcount_y"]], axis=0) # Mean of xcount_x and xcount_y
|
|
978
|
+
max_xcount = int(np.max(xcount_mean))
|
|
979
|
+
if max_xcount > 100:
|
|
980
|
+
bounds = [0, 100, 1000, max_xcount]
|
|
981
|
+
cmap = mcolors.ListedColormap(["lightcoral", "red", "darkred"]) # Light red, red, dark red
|
|
982
|
+
# Boundaries for color ranges
|
|
983
|
+
else:
|
|
984
|
+
bounds = [0, 100, max_xcount]
|
|
985
|
+
cmap = mcolors.ListedColormap(["lightcoral", "red"]) # Light red, red, dark red
|
|
986
|
+
|
|
987
|
+
norm = mcolors.BoundaryNorm(bounds, cmap.N) # Apply the custom colormap to the scatter plot based on xcount
|
|
988
|
+
|
|
989
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
990
|
+
if "error_x" in data.columns:
|
|
991
|
+
if "xcount_x" not in data.columns:
|
|
992
|
+
ax.plot(
|
|
993
|
+
data["date_cori"],
|
|
994
|
+
data["vv"],
|
|
995
|
+
linestyle="",
|
|
996
|
+
zorder=1,
|
|
997
|
+
marker="o",
|
|
998
|
+
lw=0.7,
|
|
999
|
+
markersize=2,
|
|
1000
|
+
color=color,
|
|
1001
|
+
label=label,
|
|
1002
|
+
)
|
|
1003
|
+
# Plot confidence interval using fill_between
|
|
1004
|
+
ax.fill_between(
|
|
1005
|
+
data["date_cori"],
|
|
1006
|
+
data["vv"] - data["confidence_v"],
|
|
1007
|
+
data["vv"] + data["confidence_v"],
|
|
1008
|
+
color="purple",
|
|
1009
|
+
alpha=0.4,
|
|
1010
|
+
)
|
|
1011
|
+
# Create custom legend entries for confidence interval
|
|
1012
|
+
conf_legend = malines.Line2D([], [], color="purple", alpha=0.4, lw=6, label="95% confidence interval")
|
|
1013
|
+
if "xcount_x" in data.columns:
|
|
1014
|
+
plt.subplots_adjust(bottom=-0.01)
|
|
1015
|
+
# Add the legends for confidence interval and GPS
|
|
1016
|
+
ax.legend(
|
|
1017
|
+
[conf_legend],
|
|
1018
|
+
["95% confidence interval"],
|
|
1019
|
+
loc="upper center",
|
|
1020
|
+
bbox_to_anchor=(0.5, -0.05),
|
|
1021
|
+
fontsize=15,
|
|
1022
|
+
ncol=3,
|
|
1023
|
+
markerscale=1.5,
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
if "xcount_x" in data.columns:
|
|
1027
|
+
scat = ax.scatter(data["date_cori"], data["vv"], c=xcount_mean, cmap=cmap, norm=norm, s=7)
|
|
1028
|
+
# Add the colorbar for xcount
|
|
1029
|
+
cbar = fig.colorbar(scat, ax=ax, boundaries=bounds, orientation="horizontal", pad=0.15, shrink=0.7)
|
|
1030
|
+
cbar.set_label("Number of image-pair velocities used", fontsize=14)
|
|
1031
|
+
|
|
1032
|
+
ax.set_ylabel("Velocity magnitude [m/y]", fontsize=18)
|
|
1033
|
+
# Show plot if specified
|
|
1034
|
+
if self.show:
|
|
1035
|
+
plt.show(block=False)
|
|
1036
|
+
|
|
1037
|
+
# Save the figure
|
|
1038
|
+
if self.save:
|
|
1039
|
+
fig.savefig(f"{self.path_save}/confidence_intervals_and_quality.png")
|
|
1040
|
+
|
|
1041
|
+
return fig, ax
|
|
1042
|
+
|
|
1043
|
+
# %%========================================================================= #
|
|
1044
|
+
# PLOTS ABOUT INVERSION RESULTS #
|
|
1045
|
+
# =========================================================================%% #
|
|
1046
|
+
|
|
1047
|
+
def plot_xcount_vx_vy(self, cmap: str = "viridis", block_plot: bool = True):
|
|
1048
|
+
"""
|
|
1049
|
+
Plot the observation contribution to the inversion on top of velocities x and y components.
|
|
1050
|
+
|
|
1051
|
+
:param cmap: [str] [default is 'rainbow] --- Color map used to mark the xcount values in the plots.
|
|
1052
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
1053
|
+
|
|
1054
|
+
:return fig, ax: Axis and Figure of the plot
|
|
1055
|
+
"""
|
|
1056
|
+
|
|
1057
|
+
assert self.datainvert is not None, (
|
|
1058
|
+
"No inverted data found, think of loading the results of an inversion to this pixel_class before calling plot_xcount_vx_vy()"
|
|
1059
|
+
)
|
|
1060
|
+
assert "xcount_x" in self.datainvert.dataf.columns and "xcount_y" in self.datainvert.dataf.columns, (
|
|
1061
|
+
"'xcount_x' and/or 'xount_y' values are missing in the data, impossible to plot the xcount values"
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
fig, ax = plt.subplots(2, 1, figsize=self.figsize)
|
|
1065
|
+
ax[0].set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
1066
|
+
ax[0].scatter(
|
|
1067
|
+
self.datainvert.dataf["date_cori"],
|
|
1068
|
+
self.datainvert.dataf["vx"],
|
|
1069
|
+
c=self.datainvert.dataf["xcount_x"],
|
|
1070
|
+
s=8,
|
|
1071
|
+
cmap=cmap,
|
|
1072
|
+
label="Y_contribution",
|
|
1073
|
+
)
|
|
1074
|
+
ax[1].set_ylabel(f"Vy [{self.unit}]", fontsize=14)
|
|
1075
|
+
ax[1].set_xlabel("Central dates", fontsize=14)
|
|
1076
|
+
scat = ax[1].scatter(
|
|
1077
|
+
self.datainvert.dataf["date_cori"],
|
|
1078
|
+
self.datainvert.dataf["vy"],
|
|
1079
|
+
c=self.datainvert.dataf["xcount_y"],
|
|
1080
|
+
s=8,
|
|
1081
|
+
cmap=cmap,
|
|
1082
|
+
)
|
|
1083
|
+
plt.subplots_adjust(bottom=0.1)
|
|
1084
|
+
cbar = fig.colorbar(scat, ax=ax.ravel().tolist(), orientation="horizontal", pad=0.15)
|
|
1085
|
+
cbar.set_label("Amount of contributing observations", fontsize=14)
|
|
1086
|
+
fig.suptitle(
|
|
1087
|
+
"Contribution of the observations to the resulting inverted velocity x and y components",
|
|
1088
|
+
y=0.95,
|
|
1089
|
+
fontsize=16,
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
if self.show:
|
|
1093
|
+
plt.show(block=block_plot)
|
|
1094
|
+
if self.save:
|
|
1095
|
+
fig.savefig(f"{self.path_save}/X_dates_contribution_vx_vy.png")
|
|
1096
|
+
|
|
1097
|
+
return fig, ax
|
|
1098
|
+
|
|
1099
|
+
def plot_xcount_vv(self, cmap: str = "viridis", block_plot: bool = True):
|
|
1100
|
+
"""
|
|
1101
|
+
Plot the observation contribution to the inversion on top of the velocity magnitude.
|
|
1102
|
+
|
|
1103
|
+
:param cmap: [str] [default is 'rainbow''] --- Color map used in the plots
|
|
1104
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
1105
|
+
|
|
1106
|
+
:return fig, ax: Axis and Figure of the plot
|
|
1107
|
+
"""
|
|
1108
|
+
|
|
1109
|
+
assert self.datainvert is not None, (
|
|
1110
|
+
"No inverted data found, think of loading the results of an inversion to this pixel_class before calling plot_xcount_vv()"
|
|
1111
|
+
)
|
|
1112
|
+
assert "xcount_x" in self.datainvert.dataf.columns and "xcount_y" in self.datainvert.dataf.columns, (
|
|
1113
|
+
"'xcount_x' and/or 'xount_y' values are missing in the data, impossible to plot the xcount values"
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
1117
|
+
ax.set_ylabel(f"Velocity magnitude [{self.unit}]", fontsize=14)
|
|
1118
|
+
ax.set_xlabel("Central dates", fontsize=14)
|
|
1119
|
+
scat = ax.scatter(
|
|
1120
|
+
self.datainvert.dataf["date_cori"],
|
|
1121
|
+
self.datainvert.dataf["vv"],
|
|
1122
|
+
c=(self.datainvert.dataf["xcount_x"] + self.datainvert.dataf["xcount_y"]) / 2,
|
|
1123
|
+
s=8,
|
|
1124
|
+
cmap=cmap,
|
|
1125
|
+
)
|
|
1126
|
+
# Adding a colorbar for the scatter plot
|
|
1127
|
+
cbar = plt.colorbar(scat, ax=ax, pad=0.02)
|
|
1128
|
+
cbar.set_label("Amount of contributing observations", fontsize=14)
|
|
1129
|
+
plt.subplots_adjust(bottom=0.2)
|
|
1130
|
+
fig.suptitle("Contribution of the observations to the resulting inverted velocities", y=0.95, fontsize=16)
|
|
1131
|
+
|
|
1132
|
+
if self.show:
|
|
1133
|
+
plt.show(block=block_plot)
|
|
1134
|
+
if self.save:
|
|
1135
|
+
fig.savefig(f"{self.path_save}/X_dates_contribution_vv.png")
|
|
1136
|
+
|
|
1137
|
+
return fig, ax
|
|
1138
|
+
|
|
1139
|
+
def plot_weights_inversion(self, cmap: str = "plasma_r", block_plot: bool = True):
|
|
1140
|
+
"""
|
|
1141
|
+
Plot initial and final weights used in the inversion.
|
|
1142
|
+
|
|
1143
|
+
:param cmap: [str] [default is 'plasma_r'] --- Color map used in the plots
|
|
1144
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
1145
|
+
|
|
1146
|
+
:return ax_f, fig_f, ax_l, fig_l: Axis and Figure of the plots (weights from f: the first inversion, l: the last inversion)
|
|
1147
|
+
"""
|
|
1148
|
+
|
|
1149
|
+
assert self.datainvert is not None, (
|
|
1150
|
+
"No inverted data found, think of loading the results of an inversion to this pixel_class before calling plot_xcount_vv()"
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
## ----------------------- Weights used during the first inversion ------------------------- ##
|
|
1154
|
+
fig_f, ax_f = plt.subplots(2, 1, figsize=(8, 4))
|
|
1155
|
+
ax_f[0].set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
1156
|
+
ax_f[0].set_xticklabels([])
|
|
1157
|
+
scat1 = ax_f[0].scatter(
|
|
1158
|
+
self.dataobs.dataf["date_cori"],
|
|
1159
|
+
self.dataobs.dataf["vx"],
|
|
1160
|
+
c=abs(self.dataobs.dataf["weightinix"]),
|
|
1161
|
+
s=5,
|
|
1162
|
+
cmap=cmap,
|
|
1163
|
+
edgecolors="k",
|
|
1164
|
+
linewidth=0.1,
|
|
1165
|
+
)
|
|
1166
|
+
ax_f[1].set_ylabel(f"Vy [{self.unit}]", fontsize=14)
|
|
1167
|
+
ax_f[1].set_xlabel("Central dates", fontsize=14)
|
|
1168
|
+
scat2 = ax_f[1].scatter(
|
|
1169
|
+
self.dataobs.dataf["date_cori"],
|
|
1170
|
+
self.dataobs.dataf["vx"],
|
|
1171
|
+
c=abs(self.dataobs.dataf["weightiniy"]),
|
|
1172
|
+
s=5,
|
|
1173
|
+
cmap=cmap,
|
|
1174
|
+
edgecolors="k",
|
|
1175
|
+
linewidth=0.1,
|
|
1176
|
+
)
|
|
1177
|
+
plt.subplots_adjust(bottom=0.32)
|
|
1178
|
+
legend1 = ax_f[1].legend(
|
|
1179
|
+
*scat1.legend_elements(num=5),
|
|
1180
|
+
loc="lower left",
|
|
1181
|
+
bbox_to_anchor=(0.05, -1.25),
|
|
1182
|
+
ncol=3,
|
|
1183
|
+
title="Initial weights for Vx",
|
|
1184
|
+
)
|
|
1185
|
+
legend2 = ax_f[1].legend(
|
|
1186
|
+
*scat2.legend_elements(num=5),
|
|
1187
|
+
loc="lower right",
|
|
1188
|
+
bbox_to_anchor=(0.95, -1.25),
|
|
1189
|
+
ncol=3,
|
|
1190
|
+
title="Initial weights for Vy",
|
|
1191
|
+
)
|
|
1192
|
+
ax_f[1].add_artist(legend1)
|
|
1193
|
+
ax_f[1].add_artist(legend2)
|
|
1194
|
+
fig_f.suptitle("Initial weights before the inversion", y=0.95, fontsize=16)
|
|
1195
|
+
|
|
1196
|
+
if self.show:
|
|
1197
|
+
plt.show(block=block_plot)
|
|
1198
|
+
if self.save:
|
|
1199
|
+
fig_f.savefig(f"{self.path_save}/weightini_vx_vy.png")
|
|
1200
|
+
|
|
1201
|
+
## ------------------------ Weights used during the last inversion ------------------------- ##
|
|
1202
|
+
fig_l, ax_l = plt.subplots(2, 1, figsize=(8, 4))
|
|
1203
|
+
ax_l[0].set_ylabel(f"Vx [{self.unit}]", fontsize=14)
|
|
1204
|
+
ax_l[0].set_xticklabels([])
|
|
1205
|
+
scat1 = ax_l[0].scatter(
|
|
1206
|
+
self.dataobs.dataf["date_cori"],
|
|
1207
|
+
self.dataobs.dataf["vx"],
|
|
1208
|
+
c=abs(self.dataobs.dataf["weightlastx"]),
|
|
1209
|
+
s=5,
|
|
1210
|
+
cmap=cmap,
|
|
1211
|
+
edgecolors="k",
|
|
1212
|
+
linewidth=0.1,
|
|
1213
|
+
)
|
|
1214
|
+
ax_l[1].set_ylabel(f"Vy [{self.unit}]", fontsize=14)
|
|
1215
|
+
ax_l[1].set_xlabel("Central dates", fontsize=14)
|
|
1216
|
+
scat2 = ax_l[1].scatter(
|
|
1217
|
+
self.dataobs.dataf["date_cori"],
|
|
1218
|
+
self.dataobs.dataf["vx"],
|
|
1219
|
+
c=abs(self.dataobs.dataf["weightlasty"]),
|
|
1220
|
+
s=5,
|
|
1221
|
+
cmap=cmap,
|
|
1222
|
+
edgecolors="k",
|
|
1223
|
+
linewidth=0.1,
|
|
1224
|
+
)
|
|
1225
|
+
plt.subplots_adjust(bottom=0.32)
|
|
1226
|
+
legend1 = ax_l[1].legend(
|
|
1227
|
+
*scat1.legend_elements(num=5),
|
|
1228
|
+
loc="lower left",
|
|
1229
|
+
bbox_to_anchor=(0.05, -1.25),
|
|
1230
|
+
ncol=3,
|
|
1231
|
+
title="Final weights for Vx",
|
|
1232
|
+
)
|
|
1233
|
+
legend2 = ax_l[1].legend(
|
|
1234
|
+
*scat2.legend_elements(num=5),
|
|
1235
|
+
loc="lower right",
|
|
1236
|
+
bbox_to_anchor=(0.95, -1.25),
|
|
1237
|
+
ncol=3,
|
|
1238
|
+
title="Final weights for Vy",
|
|
1239
|
+
)
|
|
1240
|
+
ax_l[1].add_artist(legend1)
|
|
1241
|
+
ax_l[1].add_artist(legend2)
|
|
1242
|
+
fig_l.suptitle("Final weights after the inversion", y=0.95, fontsize=16)
|
|
1243
|
+
|
|
1244
|
+
if self.show:
|
|
1245
|
+
plt.show(block=block_plot)
|
|
1246
|
+
if self.save:
|
|
1247
|
+
fig_l.savefig(f"{self.path_save}/weightlast_vx_vy.png")
|
|
1248
|
+
|
|
1249
|
+
return ax_f, fig_f, ax_l, fig_l
|
|
1250
|
+
|
|
1251
|
+
def plot_residuals(self, log_scale: bool = False, block_plot: bool = True):
|
|
1252
|
+
"""
|
|
1253
|
+
Statistics about the residuals from the inversion:
|
|
1254
|
+
- Plot of the final residuals overlaid in colors on vx and vy measurements ('residuals_vx_vy_final_residual.png').
|
|
1255
|
+
- Plot of the reconstructed velocity observations (from AX) overlaid on the original velocity observations ('residuals_vx_vy_mismatch.png').
|
|
1256
|
+
- Comparison of residuals according to the temporal baseline (residuals_tempbaseline.png),
|
|
1257
|
+
- the type of sensor and authors (residuals_author_abs.png,residuals_vy_author.png,residuals_vx_author_abs.png),
|
|
1258
|
+
- and the quality indicators (residuals_quality.png).
|
|
1259
|
+
|
|
1260
|
+
:param log_scale: [bool] [default is False] --- if True, plot the figure in a log scale
|
|
1261
|
+
:param block_plot: [bool] [default is True] --- If True, the plot persists on the screen until the user manually closes it. If False, it disappears instantly after plotting.
|
|
1262
|
+
"""
|
|
1263
|
+
|
|
1264
|
+
assert self.datainvert is not None, (
|
|
1265
|
+
"No inverted data found, think of loading the results of an inversion to this pixel_class before calling plot_xcount_vv()"
|
|
1266
|
+
)
|
|
1267
|
+
assert self.A is not None, "Please provide A (design matrix) when loading the pixel_class"
|
|
1268
|
+
|
|
1269
|
+
dataf = self.dataobs.dataf.replace("L. Charrier, J. Mouginot, R.Millan, A.Derkacheva", "IGE")
|
|
1270
|
+
dataf = dataf.replace("S. Leinss, L. Charrier", "Leinss")
|
|
1271
|
+
|
|
1272
|
+
dataf["abs_residux"] = abs(dataf["residux"])
|
|
1273
|
+
dataf["abs_residuy"] = abs(dataf["residuy"])
|
|
1274
|
+
|
|
1275
|
+
dataf = dataf.rename(columns={"author": "Author"})
|
|
1276
|
+
|
|
1277
|
+
conversion = self.get_conversion()
|
|
1278
|
+
|
|
1279
|
+
###RECONSTRUCTION PLOT : reconstruct the observation from AX
|
|
1280
|
+
Y_reconstruct_x = (
|
|
1281
|
+
np.dot(self.A, self.datainvert.dataf["vx"] * self.datainvert.dataf["temporal_baseline"] / conversion)
|
|
1282
|
+
/ self.dataobs.dataf["temporal_baseline"]
|
|
1283
|
+
* conversion
|
|
1284
|
+
)
|
|
1285
|
+
Y_reconstruct_y = (
|
|
1286
|
+
np.dot(self.A, self.datainvert.dataf["vy"] * self.datainvert.dataf["temporal_baseline"] / conversion)
|
|
1287
|
+
/ self.dataobs.dataf["temporal_baseline"]
|
|
1288
|
+
* conversion
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
show = copy.copy(self.show)
|
|
1292
|
+
save = copy.copy(self.save)
|
|
1293
|
+
self.show, self.save = False, False
|
|
1294
|
+
fig, ax = self.plot_vx_vy(type_data="obs")
|
|
1295
|
+
self.show, self.save = show, save
|
|
1296
|
+
|
|
1297
|
+
# fig, ax = plt.subplots(2, 1, figsize=(8, 4))
|
|
1298
|
+
ax[0].plot(
|
|
1299
|
+
self.dataobs.dataf["date_cori"],
|
|
1300
|
+
Y_reconstruct_x,
|
|
1301
|
+
linestyle="",
|
|
1302
|
+
marker="o",
|
|
1303
|
+
color="r",
|
|
1304
|
+
markersize=3,
|
|
1305
|
+
alpha=0.2,
|
|
1306
|
+
) # Display the vx components
|
|
1307
|
+
ax[0].errorbar(
|
|
1308
|
+
self.dataobs.dataf["date_cori"],
|
|
1309
|
+
Y_reconstruct_x,
|
|
1310
|
+
xerr=self.dataobs.dataf["offset_bar"],
|
|
1311
|
+
color="r",
|
|
1312
|
+
alpha=0.2,
|
|
1313
|
+
fmt=",",
|
|
1314
|
+
zorder=1,
|
|
1315
|
+
)
|
|
1316
|
+
ax[0].set_ylabel(f"Vx [{self.unit}]", fontsize=18)
|
|
1317
|
+
ax[1].plot(
|
|
1318
|
+
self.dataobs.dataf["date_cori"],
|
|
1319
|
+
Y_reconstruct_y,
|
|
1320
|
+
linestyle="",
|
|
1321
|
+
marker="o",
|
|
1322
|
+
color="r",
|
|
1323
|
+
markersize=3,
|
|
1324
|
+
alpha=0.2,
|
|
1325
|
+
label="Reconstructed Data",
|
|
1326
|
+
) # Display the vy components
|
|
1327
|
+
ax[1].errorbar(
|
|
1328
|
+
self.dataobs.dataf["date_cori"],
|
|
1329
|
+
Y_reconstruct_y,
|
|
1330
|
+
xerr=self.dataobs.dataf["offset_bar"],
|
|
1331
|
+
color="r",
|
|
1332
|
+
alpha=0.3,
|
|
1333
|
+
fmt=",",
|
|
1334
|
+
zorder=1,
|
|
1335
|
+
)
|
|
1336
|
+
ax[1].legend(bbox_to_anchor=(0.55, -0.3), ncol=3, fontsize=15)
|
|
1337
|
+
if self.show:
|
|
1338
|
+
plt.show()
|
|
1339
|
+
if self.save:
|
|
1340
|
+
fig.savefig(f"{self.path_save}/residuals_vx_vy_mismatch.png")
|
|
1341
|
+
|
|
1342
|
+
###RESIDUALS FROM THE LAST INVERSION
|
|
1343
|
+
fig, ax = plt.subplots(2, 1, figsize=(8, 4))
|
|
1344
|
+
ax[0].set_ylabel(f"Vx [{self.unit}]")
|
|
1345
|
+
scat1 = ax[0].scatter(
|
|
1346
|
+
self.dataobs.dataf["date_cori"],
|
|
1347
|
+
self.dataobs.dataf["vx"],
|
|
1348
|
+
c=abs(self.dataobs.dataf["residux"]),
|
|
1349
|
+
s=5,
|
|
1350
|
+
cmap="plasma_r",
|
|
1351
|
+
edgecolors="k",
|
|
1352
|
+
linewidth=0.1,
|
|
1353
|
+
)
|
|
1354
|
+
ax[1].set_ylabel(f"Vy [{self.unit}]")
|
|
1355
|
+
scat2 = ax[1].scatter(
|
|
1356
|
+
self.dataobs.dataf["date_cori"],
|
|
1357
|
+
self.dataobs.dataf["vy"],
|
|
1358
|
+
c=abs(self.dataobs.dataf["residuy"]),
|
|
1359
|
+
s=5,
|
|
1360
|
+
cmap="plasma_r",
|
|
1361
|
+
edgecolors="k",
|
|
1362
|
+
linewidth=0.1,
|
|
1363
|
+
)
|
|
1364
|
+
plt.subplots_adjust(bottom=0.3)
|
|
1365
|
+
legend1 = ax[1].legend(
|
|
1366
|
+
*scat1.legend_elements(num=5),
|
|
1367
|
+
loc="lower left",
|
|
1368
|
+
bbox_to_anchor=(0.05, 0),
|
|
1369
|
+
bbox_transform=fig.transFigure,
|
|
1370
|
+
ncol=3,
|
|
1371
|
+
title="Absolute residual Vx",
|
|
1372
|
+
)
|
|
1373
|
+
legend2 = ax[1].legend(
|
|
1374
|
+
*scat2.legend_elements(num=5),
|
|
1375
|
+
loc="lower right",
|
|
1376
|
+
bbox_to_anchor=(0.95, 0),
|
|
1377
|
+
bbox_transform=fig.transFigure,
|
|
1378
|
+
ncol=3,
|
|
1379
|
+
title="Absolute residual Vy",
|
|
1380
|
+
)
|
|
1381
|
+
ax[1].add_artist(legend1)
|
|
1382
|
+
ax[1].add_artist(legend2)
|
|
1383
|
+
if self.show:
|
|
1384
|
+
plt.show(block=False)
|
|
1385
|
+
if self.save:
|
|
1386
|
+
fig.savefig(f"{self.path_save}/residuals_vx_vy_final_residual.png")
|
|
1387
|
+
|
|
1388
|
+
###RESIDUALS FOR VX AND VY, ACCORDING TO THE SENSOR
|
|
1389
|
+
ax = sns.catplot(data=dataf, x="sensor", y="abs_residux", hue="Author", kind="box")
|
|
1390
|
+
ax.set(xlabel="Sensor", ylabel="Absolute residual vx [m/y]")
|
|
1391
|
+
if self.save:
|
|
1392
|
+
plt.savefig(f"{self.path_save}/residuals_vx_author_abs.png")
|
|
1393
|
+
if self.show:
|
|
1394
|
+
plt.show()
|
|
1395
|
+
|
|
1396
|
+
ax = sns.catplot(data=dataf, x="sensor", y="abs_residuy", hue="Author", kind="box")
|
|
1397
|
+
ax.set(xlabel="Sensor", ylabel="Absolute residual vy [m/y]")
|
|
1398
|
+
if self.save:
|
|
1399
|
+
plt.savefig(f"{self.path_save}/residuals_author_abs.png")
|
|
1400
|
+
if self.show:
|
|
1401
|
+
plt.show()
|
|
1402
|
+
|
|
1403
|
+
###RESIDUALS FROM VX AND VY, ACCORDING TO THE AUTHOR
|
|
1404
|
+
ax = sns.catplot(data=dataf, x="sensor", y="residux", hue="Author", kind="box")
|
|
1405
|
+
ax.set(xlabel="Sensor", ylabel="Residual vx [m/y]")
|
|
1406
|
+
if self.save:
|
|
1407
|
+
plt.savefig(f"{self.path_save}/residuals_vx_author.png")
|
|
1408
|
+
if self.show:
|
|
1409
|
+
plt.show()
|
|
1410
|
+
|
|
1411
|
+
ax = sns.catplot(data=dataf, x="sensor", y="residuy", hue="Author", kind="box")
|
|
1412
|
+
ax.set(xlabel="Sensor", ylabel="Residual vy [m/y]")
|
|
1413
|
+
if self.save:
|
|
1414
|
+
plt.savefig(f"{self.path_save}/residuals_vy_author.png")
|
|
1415
|
+
if self.show:
|
|
1416
|
+
plt.show()
|
|
1417
|
+
|
|
1418
|
+
###RESIDUALS FROM VX AND VY, ACCORDING TO THE QUALITY INDICATOR
|
|
1419
|
+
fig, ax = plt.subplots(2, 1, figsize=self.figsize)
|
|
1420
|
+
color_list = ["b", "m", "k", "g", "m"]
|
|
1421
|
+
for i, auth in enumerate(dataf["Author"].unique()):
|
|
1422
|
+
ax[0].plot(
|
|
1423
|
+
dataf[dataf["Author"] == auth]["weightinix"],
|
|
1424
|
+
dataf[dataf["Author"] == auth]["residux"],
|
|
1425
|
+
linestyle="",
|
|
1426
|
+
marker="o",
|
|
1427
|
+
color=color_list[i],
|
|
1428
|
+
markersize=3,
|
|
1429
|
+
)
|
|
1430
|
+
ax[1].plot(
|
|
1431
|
+
dataf[dataf["Author"] == auth]["weightiniy"],
|
|
1432
|
+
dataf[dataf["Author"] == auth]["residuy"],
|
|
1433
|
+
linestyle="",
|
|
1434
|
+
marker="o",
|
|
1435
|
+
color=color_list[i],
|
|
1436
|
+
markersize=3,
|
|
1437
|
+
label=auth,
|
|
1438
|
+
)
|
|
1439
|
+
if log_scale:
|
|
1440
|
+
ax[0].set_yscale("log")
|
|
1441
|
+
ax[1].set_yscale("log")
|
|
1442
|
+
ax[0].set_ylabel(f"Residual vx [{self.unit}]", fontsize=16)
|
|
1443
|
+
ax[1].set_ylabel(f"Residual vy [{self.unit}]", fontsize=16)
|
|
1444
|
+
ax[1].set_xlabel("Quality indicator", fontsize=16)
|
|
1445
|
+
plt.subplots_adjust(bottom=0.2)
|
|
1446
|
+
ax[1].legend(loc="lower left", bbox_to_anchor=(0.12, 0), bbox_transform=fig.transFigure, fontsize=12, ncol=5)
|
|
1447
|
+
if self.show:
|
|
1448
|
+
plt.show()
|
|
1449
|
+
if self.save:
|
|
1450
|
+
if log_scale:
|
|
1451
|
+
fig.savefig(f"{self.path_save}/residu_qualitylog.png")
|
|
1452
|
+
else:
|
|
1453
|
+
fig.savefig(f"{self.path_save}/residuals_quality.png")
|
|
1454
|
+
|
|
1455
|
+
###RESIDUALS FROM VX AND VY, ACCORDING TO THE TEMPORAL BASELINE
|
|
1456
|
+
fig, ax = plt.subplots(2, 1, figsize=self.figsize)
|
|
1457
|
+
color_list = ["b", "m", "k", "g", "m"]
|
|
1458
|
+
for i, auth in enumerate(dataf["Author"].unique()):
|
|
1459
|
+
ax[0].plot(
|
|
1460
|
+
np.array(dataf["temporal_baseline"])[dataf["Author"] == auth] * 2,
|
|
1461
|
+
dataf[dataf["Author"] == auth]["residux"],
|
|
1462
|
+
linestyle="",
|
|
1463
|
+
marker="o",
|
|
1464
|
+
color=color_list[i],
|
|
1465
|
+
markersize=3,
|
|
1466
|
+
)
|
|
1467
|
+
ax[1].plot(
|
|
1468
|
+
np.array(dataf["temporal_baseline"])[dataf["Author"] == auth] * 2,
|
|
1469
|
+
dataf[dataf["Author"] == auth]["residuy"],
|
|
1470
|
+
linestyle="",
|
|
1471
|
+
marker="o",
|
|
1472
|
+
color=color_list[i],
|
|
1473
|
+
markersize=3,
|
|
1474
|
+
label=auth,
|
|
1475
|
+
)
|
|
1476
|
+
if log_scale:
|
|
1477
|
+
ax[0].set_yscale("log")
|
|
1478
|
+
ax[1].set_yscale("log")
|
|
1479
|
+
ax[0].set_ylabel(f"Residual vx [{self.unit}]", fontsize=16)
|
|
1480
|
+
ax[1].set_ylabel(f"Residual vy [{self.unit}]", fontsize=16)
|
|
1481
|
+
ax[1].set_xlabel("Temporal baseline [days]", fontsize=16)
|
|
1482
|
+
plt.subplots_adjust(bottom=0.2)
|
|
1483
|
+
ax[1].legend(loc="lower left", bbox_to_anchor=(0.12, 0), bbox_transform=fig.transFigure, fontsize=12, ncol=5)
|
|
1484
|
+
if self.show:
|
|
1485
|
+
plt.show()
|
|
1486
|
+
if self.save:
|
|
1487
|
+
if log_scale:
|
|
1488
|
+
fig.savefig(f"{self.path_save}/residu_tempbaseline_log.png")
|
|
1489
|
+
else:
|
|
1490
|
+
fig.savefig(f"{self.path_save}/residuals_tempbaseline.png")
|
|
1491
|
+
|
|
1492
|
+
# %%========================================================================= #
|
|
1493
|
+
# PLOTS ABOUT THE SEASONALITY #
|
|
1494
|
+
# =========================================================================%% #
|
|
1495
|
+
|
|
1496
|
+
def plot_filtered_results(self, filt: str | None = None, impose_frequency: bool = True):
|
|
1497
|
+
"""
|
|
1498
|
+
Plot the filtered TICOI results, with a given filter.
|
|
1499
|
+
|
|
1500
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
1501
|
+
:param impose_frequency: [bool] [default is True] --- If True, impose the frequency to 1/365.25 days-1 (one year seasonality). If False, look for the best matching frequency too, using the Fourier Transform in the first place
|
|
1502
|
+
|
|
1503
|
+
:return fig, ax: Axis and Figure of the plot
|
|
1504
|
+
"""
|
|
1505
|
+
|
|
1506
|
+
vv_filt, vv_c, dates_c, dates = self.get_filtered_results(filt=filt)
|
|
1507
|
+
|
|
1508
|
+
if impose_frequency:
|
|
1509
|
+
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=self.figsize)
|
|
1510
|
+
axe = ax
|
|
1511
|
+
else:
|
|
1512
|
+
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=self.figsize)
|
|
1513
|
+
axe = ax[0]
|
|
1514
|
+
|
|
1515
|
+
axe.plot(dates_c, vv_c, "blue", label="Before filtering")
|
|
1516
|
+
axe.plot(dates_c, vv_filt, "red", label="After filtering")
|
|
1517
|
+
axe.set_xlabel("Centered velocity [m/y]", fontsize=16)
|
|
1518
|
+
axe.set_ylabel("Central date", fontsize=16)
|
|
1519
|
+
axe.set_title("Effect of filtering", fontsize=16)
|
|
1520
|
+
axe.legend(loc="lower left")
|
|
1521
|
+
|
|
1522
|
+
if not impose_frequency:
|
|
1523
|
+
ax[1].plot(dates_c, vv_filt * signal.windows.hann(len(dates)), "blue", label="With Hanning windowing")
|
|
1524
|
+
ax[1].plot(dates_c, vv_filt, "black", label="Without windowing")
|
|
1525
|
+
ax[1].set_xlabel("Centered velocity [m/y]", fontsize=16)
|
|
1526
|
+
ax[1].set_ylabel("Central date", fontsize=16)
|
|
1527
|
+
ax[1].set_title("Effect of Hanning windowing", fontsize=16)
|
|
1528
|
+
ax[1].legend(loc="best")
|
|
1529
|
+
|
|
1530
|
+
fig.tight_layout()
|
|
1531
|
+
|
|
1532
|
+
if self.show:
|
|
1533
|
+
plt.show()
|
|
1534
|
+
if self.save:
|
|
1535
|
+
fig.savefig(f"{self.path_save}/filtered_results.png")
|
|
1536
|
+
|
|
1537
|
+
return fig, ax
|
|
1538
|
+
|
|
1539
|
+
def plot_TF(self, filt=None, verbose=False):
|
|
1540
|
+
"""
|
|
1541
|
+
Plot the Fourier Transform (TF) of the TICOI results after filtering with a given filter.
|
|
1542
|
+
|
|
1543
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
1544
|
+
:param verbose:[bool] [default is False] --- If True, print the maximum and the amplitude of the TF
|
|
1545
|
+
|
|
1546
|
+
:return fig, ax: Axis and Figure of the plot
|
|
1547
|
+
"""
|
|
1548
|
+
|
|
1549
|
+
vv_tf, vv_win_tf, freq, N = self.get_TF(filt=filt, verbose=verbose)
|
|
1550
|
+
|
|
1551
|
+
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=self.figsize)
|
|
1552
|
+
ax.plot(freq, 2 / N * np.abs(vv_tf), "blue", label="TF without windowing")
|
|
1553
|
+
ax.plot(freq, 2 / N * np.abs(vv_win_tf), "red", label="TF after Hanning windowing")
|
|
1554
|
+
ax.vlines(
|
|
1555
|
+
[i / 365 for i in range(1, 4)],
|
|
1556
|
+
0,
|
|
1557
|
+
1.1 * 2 / N * max(np.max(np.abs(vv_tf)), np.max(np.abs(vv_win_tf))),
|
|
1558
|
+
color="black",
|
|
1559
|
+
label="365d periodicity",
|
|
1560
|
+
)
|
|
1561
|
+
ax.set_xlim([0, 0.01])
|
|
1562
|
+
ax.set_ylim([0, 1.1 * 2 / N * max(np.max(np.abs(vv_tf)), np.max(np.abs(vv_win_tf)))])
|
|
1563
|
+
ax.set_xlabel("Frequency [day-1]", fontsize=16)
|
|
1564
|
+
ax.set_ylabel("Amplitude [m/y]", fontsize=16)
|
|
1565
|
+
ax.legend(loc="best")
|
|
1566
|
+
ax.set_title("Fourier Transform of the TICOI-resulting velocities", fontsize=16)
|
|
1567
|
+
|
|
1568
|
+
if self.show:
|
|
1569
|
+
plt.show()
|
|
1570
|
+
if self.save:
|
|
1571
|
+
fig.savefig(f"{self.path_save}TF.png")
|
|
1572
|
+
|
|
1573
|
+
return fig, ax
|
|
1574
|
+
|
|
1575
|
+
def plot_best_matching_sinus(
|
|
1576
|
+
self,
|
|
1577
|
+
filt: str | None = None,
|
|
1578
|
+
impose_frequency: bool = True,
|
|
1579
|
+
raw_seasonality: bool = False,
|
|
1580
|
+
several_freq: int = 1,
|
|
1581
|
+
verbose: bool = False,
|
|
1582
|
+
):
|
|
1583
|
+
"""
|
|
1584
|
+
Plot the best matching sinus to the TICOI results (and to the raw data if required), by fixing the frequency to 1/365.25 days-1 or looking for the best matching one.
|
|
1585
|
+
|
|
1586
|
+
:param filt: [str | None] [default is None] --- Filter to be used ('highpass' for a highpass filtering removing the trend over several years, 'lowpass' to just respect Shannon criterium, or None to don't apply any filter)
|
|
1587
|
+
:param impose_frequency: [bool] [default is True] --- If True, impose the frequency to 1/365.25 days-1 (one year seasonality). If False, look for the best matching frequency too, using the Fourier Transform in the first place
|
|
1588
|
+
:param raw_seasonality: [bool] [default is False] --- Also look for the best matching sinus directly on the raw data
|
|
1589
|
+
:param several_freq: [int] [default is 1] --- Number of harmonics to be computed (combination of sinus at frequencies 1/365.25, 2/365.25, etc...). If 1, only compute the fundamental
|
|
1590
|
+
:param verbose: [bool] [default is False] --- If True, print the amplitude, the position of the maximum and the RMSE between the best matching sinus and the original data (TICOI results and raw data), and the best matching frequency if impose_frequency is False
|
|
1591
|
+
|
|
1592
|
+
:return fig, ax: Axis and Figure of the plots
|
|
1593
|
+
"""
|
|
1594
|
+
|
|
1595
|
+
sine_f, popt, popt_raw, dates, vv_filt, stats, stats_raw = self.get_best_matching_sinus(
|
|
1596
|
+
filt=filt,
|
|
1597
|
+
impose_frequency=impose_frequency,
|
|
1598
|
+
raw_seasonality=raw_seasonality,
|
|
1599
|
+
several_freq=several_freq,
|
|
1600
|
+
verbose=verbose,
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
sine = sine_f(dates[0], *popt, freqs=several_freq)
|
|
1604
|
+
f = popt[1] if not impose_frequency else 1 / 365.25
|
|
1605
|
+
|
|
1606
|
+
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12, 6))
|
|
1607
|
+
ax.plot(
|
|
1608
|
+
self.dataobs.dataf.index,
|
|
1609
|
+
self.dataobs.dataf["vv"],
|
|
1610
|
+
linestyle="",
|
|
1611
|
+
marker="x",
|
|
1612
|
+
markersize=2,
|
|
1613
|
+
color="orange",
|
|
1614
|
+
label="Raw data",
|
|
1615
|
+
)
|
|
1616
|
+
ax.plot(dates[1], self.datainterp.dataf["vv"], "black", alpha=0.6, label="TICOI velocities")
|
|
1617
|
+
if filt is not None:
|
|
1618
|
+
ax.plot(
|
|
1619
|
+
dates[1],
|
|
1620
|
+
vv_filt + np.mean(self.datainterp.dataf["vv"]),
|
|
1621
|
+
"red",
|
|
1622
|
+
alpha=0.6,
|
|
1623
|
+
label="Filtered TICOI velocities",
|
|
1624
|
+
)
|
|
1625
|
+
if impose_frequency and raw_seasonality:
|
|
1626
|
+
sine_raw = sine_f(dates[2], *popt_raw, freqs=several_freq) if impose_frequency else sine_f(dates[2], *popt)
|
|
1627
|
+
ax.plot(
|
|
1628
|
+
self.dataobs.dataf.index,
|
|
1629
|
+
sine_raw + self.dataobs.dataf["vv"].mean(),
|
|
1630
|
+
linewidth=3,
|
|
1631
|
+
color="forestgreen",
|
|
1632
|
+
label="Best matching sinus to raw data",
|
|
1633
|
+
)
|
|
1634
|
+
ax.plot(
|
|
1635
|
+
dates[1],
|
|
1636
|
+
sine + np.mean(self.datainterp.dataf["vv"]),
|
|
1637
|
+
color="deepskyblue",
|
|
1638
|
+
linewidth=3,
|
|
1639
|
+
label="Best matching sinus to TICOI results",
|
|
1640
|
+
)
|
|
1641
|
+
ax.vlines(
|
|
1642
|
+
pd.date_range(start=stats[0], end=self.datainterp.dataf["date2"].max(), freq=f"{int(1 / f)}D"),
|
|
1643
|
+
np.min(self.datainterp.dataf["vv"]),
|
|
1644
|
+
np.max(self.datainterp.dataf["vv"]),
|
|
1645
|
+
"black",
|
|
1646
|
+
label="Maximum (TICOI)",
|
|
1647
|
+
)
|
|
1648
|
+
ax.set_xlabel("Central dates", fontsize=16)
|
|
1649
|
+
ax.set_ylabel("Velocity", fontsize=16)
|
|
1650
|
+
ax.legend(loc="best")
|
|
1651
|
+
ax.set_title("Best matching sinus around an annual seasonality")
|
|
1652
|
+
|
|
1653
|
+
if self.show:
|
|
1654
|
+
plt.show()
|
|
1655
|
+
if self.save:
|
|
1656
|
+
fig.savefig(f"{self.path_save}matching_sine.png")
|
|
1657
|
+
|
|
1658
|
+
return fig, ax
|
|
1659
|
+
|
|
1660
|
+
def plot_annual_curves(
|
|
1661
|
+
self,
|
|
1662
|
+
normalize: bool = False,
|
|
1663
|
+
statistics: List[str] = [
|
|
1664
|
+
"min",
|
|
1665
|
+
"max",
|
|
1666
|
+
"mean",
|
|
1667
|
+
"median",
|
|
1668
|
+
"std",
|
|
1669
|
+
"amplitude",
|
|
1670
|
+
"max_day",
|
|
1671
|
+
"nb_peaks",
|
|
1672
|
+
"relative_max",
|
|
1673
|
+
],
|
|
1674
|
+
cmap: str = "hsv",
|
|
1675
|
+
markers: List[str] = [".", "p", "s", "v", "D", "*", "x", "1", "+"],
|
|
1676
|
+
markers_size: List[int] = [5, 4, 3, 4, 3, 4, 4, 7, 4],
|
|
1677
|
+
verbose: bool = True,
|
|
1678
|
+
):
|
|
1679
|
+
"""
|
|
1680
|
+
Plot the velocity curves of each year on top of ones another and compute some statistics about it ().
|
|
1681
|
+
|
|
1682
|
+
:param normalize: [bool] [default is False] --- Normalize the curves to [0-1] before plotting
|
|
1683
|
+
:param statistics: [List[str]] [default is everything] --- List of the statistics to compute and return (in ['min_max', 'mean', 'median', 'std', 'amplitude', 'max_day', 'nb_peaks', 'relative_max'])
|
|
1684
|
+
:param cmap: [str] [default is 'hsv'] --- Color map among which the colors for plotting the annual curves are picked
|
|
1685
|
+
:param markers: [List[str]] [default is ['.', 'p', 's', 'v', 'D', '*', 'x', '1', '+']] --- Symbols of the markers for the plot
|
|
1686
|
+
:param markers_size: [List[int]] [default is [5, 4, 3, 4, 3, 4, 4, 6, 4]] --- Marker size to use for each marker
|
|
1687
|
+
:param verbose: [bool] [default is False] --- Print a recap of the year statistics for each year
|
|
1688
|
+
|
|
1689
|
+
:return fig, ax: Axis and Figure of the plots
|
|
1690
|
+
:return stats: [dict] --- dictionary of the statistics (each key is associated to a list with every year's value of the statistic related to the key)
|
|
1691
|
+
"""
|
|
1692
|
+
|
|
1693
|
+
dates_c = (
|
|
1694
|
+
self.datainterp.dataf["date1"] + (self.datainterp.dataf["date2"] - self.datainterp.dataf["date1"]) // 2
|
|
1695
|
+
) # Central dates
|
|
1696
|
+
vv = np.sqrt(
|
|
1697
|
+
self.datainterp.dataf["vx"] ** 2 + self.datainterp.dataf["vy"] ** 2
|
|
1698
|
+
).to_numpy() # Velocity magnitude
|
|
1699
|
+
|
|
1700
|
+
years = np.unique(np.array([dates_c.iloc[i].year for i in range(dates_c.size)]))
|
|
1701
|
+
months_start = {
|
|
1702
|
+
"January": 1,
|
|
1703
|
+
"February": 32,
|
|
1704
|
+
"March": 60,
|
|
1705
|
+
"April": 91,
|
|
1706
|
+
"May": 121,
|
|
1707
|
+
"June": 152,
|
|
1708
|
+
"July": 182,
|
|
1709
|
+
"August": 213,
|
|
1710
|
+
"September": 244,
|
|
1711
|
+
"October": 274,
|
|
1712
|
+
"November": 305,
|
|
1713
|
+
"December": 335,
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
stats = {
|
|
1717
|
+
"min": [],
|
|
1718
|
+
"max": [],
|
|
1719
|
+
"mean": [],
|
|
1720
|
+
"median": [],
|
|
1721
|
+
"std": [],
|
|
1722
|
+
"amplitude": [],
|
|
1723
|
+
"max_day": [],
|
|
1724
|
+
"nb_peaks": [],
|
|
1725
|
+
"relative_max": [],
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
cmap = matplotlib.cm.get_cmap(cmap)
|
|
1729
|
+
colors = [cmap(i) for i in np.linspace(0, 1, len(years))]
|
|
1730
|
+
fig, ax = plt.subplots(figsize=(12, 4))
|
|
1731
|
+
for y in range(len(years)):
|
|
1732
|
+
dates = dates_c[[dates_c.iloc[i].year == years[y] for i in range(dates_c.size)]] - pd.Timestamp(
|
|
1733
|
+
year=years[y], month=1, day=1
|
|
1734
|
+
)
|
|
1735
|
+
dates = np.array([dates.iloc[i].days for i in range(dates.size)])
|
|
1736
|
+
vv_y = vv[[dates_c.iloc[i].year == years[y] for i in range(dates_c.size)]]
|
|
1737
|
+
|
|
1738
|
+
if verbose:
|
|
1739
|
+
print(f"Year {years[y]} :")
|
|
1740
|
+
|
|
1741
|
+
if "min" in statistics: # Min value of the velocities over the year
|
|
1742
|
+
stats["min"].append(np.min(vv_y))
|
|
1743
|
+
if verbose:
|
|
1744
|
+
print(" Min = {:.1f} m/y".format(stats["min"][y]))
|
|
1745
|
+
if "max" in statistics: # Max value of the velocities over the year
|
|
1746
|
+
stats["max"].append(np.max(vv_y))
|
|
1747
|
+
if verbose:
|
|
1748
|
+
print(" Max = {:.1f} m/y".format(stats["max"][y]))
|
|
1749
|
+
if "mean" in statistics:
|
|
1750
|
+
stats["mean"].append(np.mean(vv_y)) # Mean value of the velocities over the year
|
|
1751
|
+
if verbose:
|
|
1752
|
+
print(" Mean = {:.1f} m/y".format(stats["mean"][y]))
|
|
1753
|
+
if "median" in statistics:
|
|
1754
|
+
stats["median"].append(np.median(vv_y)) # Median value of the velocities over the year
|
|
1755
|
+
if verbose:
|
|
1756
|
+
print(" Median = {:.1f} m/y".format(stats["median"][y]))
|
|
1757
|
+
if "std" in statistics:
|
|
1758
|
+
stats["std"].append(np.std(vv_y, ddof=0)) # Standard deviation of the velocities over the year
|
|
1759
|
+
if verbose:
|
|
1760
|
+
print(" Standard deviation = {:.1f} m/y".format(stats["std"][y]))
|
|
1761
|
+
if "amplitude" in statistics:
|
|
1762
|
+
stats["amplitude"].append(
|
|
1763
|
+
(np.max(vv_y) - np.min(vv_y)) / 2
|
|
1764
|
+
) # Amplitude of the velocity variations (computed as (max - min)/2)
|
|
1765
|
+
if verbose:
|
|
1766
|
+
print(" Amplitude = {:.1f} m/y".format(stats["amplitude"][y]))
|
|
1767
|
+
if "max_day" in statistics:
|
|
1768
|
+
stats["max_day"].append(dates[np.argmax(vv_y)]) # Position of the maximum (in day)
|
|
1769
|
+
if verbose:
|
|
1770
|
+
diff_month = stats["max_day"][y] - np.array(list(months_start.values()))
|
|
1771
|
+
month = list(months_start.keys())[np.argmin(diff_month[diff_month > 0])]
|
|
1772
|
+
day = np.min(diff_month[diff_month > 0]) + 1
|
|
1773
|
+
print(f" Day of the maximum = {stats['max_day'][y]}th day of the year ({month}, {day})")
|
|
1774
|
+
|
|
1775
|
+
if "nb_peaks" in statistics or "relative_max" in statistics or "start_accel" in statistics:
|
|
1776
|
+
deriv = np.diff(vv_y) / np.diff(dates) # Compute the derivative of the velocities
|
|
1777
|
+
peak_pos = (
|
|
1778
|
+
[False]
|
|
1779
|
+
+ [(np.sign(deriv[i + 1]) == -1 and np.sign(deriv[i]) == 1) for i in range(len(deriv) - 1)]
|
|
1780
|
+
+ [False]
|
|
1781
|
+
)
|
|
1782
|
+
peak_dates = dates[peak_pos]
|
|
1783
|
+
peak_amplitudes = vv_y[peak_pos] - np.mean(vv_y) # This time, the amplitudes are compute as max - mean
|
|
1784
|
+
|
|
1785
|
+
if "nb_peaks" in statistics:
|
|
1786
|
+
stats["nb_peaks"].append(len(peak_dates)) # Number of velocitiy peaks during the year
|
|
1787
|
+
if verbose:
|
|
1788
|
+
print(" Number of maximum = {}".format(stats["nb_peaks"][y]))
|
|
1789
|
+
if (
|
|
1790
|
+
"relative_max" in statistics
|
|
1791
|
+
): # Amplitude of the second maximum divided by the amplitude of the first maximum
|
|
1792
|
+
if len(peak_dates) == 0:
|
|
1793
|
+
stats["relative_max"].append(None)
|
|
1794
|
+
else:
|
|
1795
|
+
stats["relative_max"].append(
|
|
1796
|
+
np.max(peak_amplitudes[np.arange(len(peak_amplitudes)) != np.argmax(peak_amplitudes)])
|
|
1797
|
+
/ np.max(peak_amplitudes)
|
|
1798
|
+
)
|
|
1799
|
+
if verbose:
|
|
1800
|
+
print(" Relative maximum value = {:.2f}".format(stats["relative_max"][y]))
|
|
1801
|
+
if "start_accel" in statistics:
|
|
1802
|
+
pass
|
|
1803
|
+
|
|
1804
|
+
if normalize:
|
|
1805
|
+
vv_y = (vv_y - np.min(vv_y)) / (np.max(vv_y) - np.min(vv_y))
|
|
1806
|
+
|
|
1807
|
+
ax.plot(
|
|
1808
|
+
dates,
|
|
1809
|
+
vv_y,
|
|
1810
|
+
linestyle="",
|
|
1811
|
+
marker=markers[y],
|
|
1812
|
+
markersize=markers_size[y],
|
|
1813
|
+
label=str(years[y]),
|
|
1814
|
+
color=colors[y],
|
|
1815
|
+
)
|
|
1816
|
+
|
|
1817
|
+
ax.set_xticks(list(months_start.values()), list(months_start.keys()))
|
|
1818
|
+
plt.setp(ax.get_xticklabels(), rotation=20, ha="right", rotation_mode="anchor")
|
|
1819
|
+
ax.set_xlabel("Day of the year", fontsize=14)
|
|
1820
|
+
ax.set_ylabel("Velocity magnitude [m/y]", fontsize=14)
|
|
1821
|
+
ax.legend(loc="best")
|
|
1822
|
+
ax.set_title("Superposed annual TICOI resulting velocities", fontsize=16)
|
|
1823
|
+
plt.subplots_adjust(bottom=0.2)
|
|
1824
|
+
|
|
1825
|
+
if self.show:
|
|
1826
|
+
plt.show()
|
|
1827
|
+
if self.save:
|
|
1828
|
+
fig.savefig(f"{self.path_save}annual_curves.png")
|
|
1829
|
+
|
|
1830
|
+
return fig, ax, {key: stats[key] for key in statistics}
|