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.

@@ -0,0 +1,209 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import scipy.fft as fft
4
+ import scipy.signal as signal
5
+ from scipy.optimize import curve_fit
6
+
7
+
8
+ def match_sine(
9
+ d: pd.DataFrame,
10
+ filt: str | None = None,
11
+ impose_frequency: bool = True,
12
+ several_freq: int | None = None,
13
+ raw_seasonality: bool = False,
14
+ d_raw: pd.DataFrame | None = None,
15
+ variable: str = "vv",
16
+ ):
17
+ """
18
+ Match a sine curve to TICOI results to look for a periodicity among the velocities. The period can either
19
+ be set to 365.25 days, or estimated along with the other parameters (amplitude, phase, offset).
20
+
21
+ :param d: [pd dataframe] --- pandas dataframe of the data at the considered pixel (TICOI results)
22
+ :param filt: which filter to use before processing the sinus ('highpass', 'lowpass' or None, default None)
23
+ :param impose_frequency: [bool] [default is True] --- Whether we should impose the frequency to 1/365.25 or not (default True)
24
+ :param several_freq: [int | None] [default is None] --- If > 1, a signal made of several frequencies (at n*f) is matched to the data
25
+ :param raw_seasonality: [bool] [default is False] --- If True, we also match a sinus to the raw data
26
+ :param d_raw: [pd dataframe | None] [default is None] --- If raw_seasonality is True, must be the dataframe of the raw velocity data (not displacements)
27
+ :param variable: [str] [default is 'vv'] --- variable used to fit the sinus (vx, vy or vv). vv correspond to the velocity magnitude
28
+ """
29
+
30
+ d = d.dropna()
31
+ dates = (d["date1"] + (d["date2"] - d["date1"]) // 2 - d["date1"].min()).dt.days.to_numpy()
32
+
33
+ N = len(dates)
34
+ if N <= 4: # do not compute anything
35
+ if raw_seasonality:
36
+ return np.nan, np.nan, np.nan, np.nan, np.nan
37
+ return np.nan, np.nan, np.nan
38
+ if variable == "vv":
39
+ vv = np.sqrt(d["vx"] ** 2 + d["vy"] ** 2).to_numpy()
40
+ elif variable == "direction":
41
+ vv = np.arctan2(d["vy"], d["vx"]).to_numpy()
42
+ else:
43
+ vv = d[variable]
44
+
45
+ Ts = dates[1] - dates[0]
46
+
47
+ # Filtering to remove inter-annual variations
48
+ if filt == "highpass":
49
+ b, a = signal.butter(4, [1 / (1.5 * 365), 1 / (2.001 * Ts)], "bandpass", fs=1 / Ts, output="ba")
50
+ vv_filt = signal.filtfilt(b, a, vv - np.mean(vv))
51
+ elif filt == "lowpass":
52
+ sos = signal.butter(4, 1 / (2.001 * Ts), "lowpass", fs=1 / Ts, output="sos")
53
+ vv_filt = signal.sosfilt(sos, vv - np.mean(vv))
54
+ else:
55
+ vv_filt = vv
56
+
57
+ # Frequency is set to 1/365.25 (one year)
58
+ if impose_frequency:
59
+
60
+ def sine_fconst(t, *args, freqs=1, f=1 / 365.25):
61
+ sine = args[0] * np.sin(
62
+ 2 * np.pi * f * t + args[1]
63
+ ) # args[0] amplitude of the signal, and args[1] the phase of the signal
64
+ for freq in range(1, freqs):
65
+ sine += args[2 * freq] * np.sin(2 * np.pi * (freq + 1) * f * t + args[2 * freq + 1])
66
+ return sine + args[-1]
67
+
68
+ try:
69
+ # Find the best matching sinus to TICOI results
70
+ if several_freq is None:
71
+ several_freq = 1
72
+ guess = np.concatenate(
73
+ [np.concatenate([[np.max(vv_filt) - np.min(vv_filt), 0] for _ in range(several_freq)]), [0]]
74
+ )
75
+ popt, pcov = curve_fit(lambda t, *args: sine_fconst(t, *args, freqs=several_freq), dates, vv_filt, p0=guess)
76
+
77
+ sine_year = sine_fconst(np.linspace(1, 365, 365), *popt, freqs=several_freq)
78
+ A = np.max(sine_year) - popt[-1]
79
+ f = 1 / 365.25
80
+ first_max_day = pd.Timedelta(np.argmax(sine_year), "D") + d["date1"].min() # date of the maximum
81
+ max_day = (first_max_day - pd.Timestamp(year=first_max_day.year, month=1, day=1)).days # day of the year
82
+ del sine_year
83
+
84
+ if raw_seasonality:
85
+ # Find the best matching sinus to raw data
86
+ dates_raw = (d_raw.index - d["date1"].min()).days.to_numpy()
87
+ raw_c = d_raw["vv"] - d_raw["vv"].mean()
88
+ guess_raw = np.concatenate(
89
+ [np.concatenate([[np.max(raw_c) - np.min(raw_c), 0] for _ in range(several_freq)]), [0]]
90
+ )
91
+ popt_raw, pcov_raw = curve_fit(
92
+ lambda t, *args: sine_fconst(t, *args, freqs=several_freq), dates_raw, raw_c, p0=guess_raw
93
+ )
94
+
95
+ sine_raw_year = sine_fconst(np.linspace(1, 365, 365), *popt_raw, freqs=several_freq)
96
+ A_raw = np.max(sine_raw_year) - popt_raw[-1]
97
+ first_max_day_raw = pd.Timedelta(np.argmax(sine_raw_year), "D") + d["date1"].min()
98
+ max_day_raw = (first_max_day_raw - pd.Timestamp(year=first_max_day.year, month=1, day=1)).days
99
+ del sine_raw_year
100
+
101
+ except RuntimeError:
102
+ A, f, max_day = np.nan, np.nan, np.nan
103
+ if raw_seasonality:
104
+ A_raw, max_day_raw = np.nan, np.nan
105
+
106
+ # Frequency is to be found too
107
+ else: # use fft, with an hanning window
108
+ n = 64 * N
109
+ window = signal.windows.hann(N)
110
+ vv_win_tf = fft.rfft(vv_filt * window, n=n)
111
+ freq = fft.rfftfreq(n, d=Ts)
112
+
113
+ # Match a sinus to the data
114
+ def sine_fvar(t, A, f, phi, off):
115
+ return A * np.sin(2 * np.pi * f * t + phi) + off
116
+
117
+ # Initial guess of the best matching sinus parameters
118
+ guess = np.array(
119
+ [
120
+ np.max(2 / N * np.abs(vv_win_tf)),
121
+ freq[np.argmax(np.abs(vv_win_tf))],
122
+ np.angle(vv_win_tf)[np.argmax(np.abs(vv_win_tf))],
123
+ np.mean(vv),
124
+ ],
125
+ dtype="float",
126
+ )
127
+
128
+ try:
129
+ popt, pcov = curve_fit(sine_fvar, dates, vv, p0=guess)
130
+
131
+ sine_year = sine_fvar(np.linspace(1, 365, 365), *popt, freqs=several_freq)
132
+ A = np.max(sine_year) - popt[-1]
133
+ first_max_day = pd.Timedelta(np.argmax(sine_year), "D") + d["date1"].min()
134
+ max_day = (first_max_day - pd.Timestamp(year=first_max_day.year, month=1, day=1)).days
135
+ del sine_year
136
+
137
+ except RuntimeError:
138
+ A, f, max_day = np.nan, np.nan, np.nan
139
+
140
+ # Return Period, amplitude and phase of the periodicity
141
+ if impose_frequency and raw_seasonality:
142
+ return 1 / f, A, max_day, A_raw, max_day_raw
143
+ else:
144
+ return 1 / f, A, max_day
145
+
146
+
147
+ def rolling_std(raw, dataf_lp, local_var_method="uniform_7d"):
148
+ """
149
+ Compute Amplitude to local VARiations index, which compares the amplitude of the best matching sinus to the standard
150
+ deviation of the noise using one of the four given methods.
151
+
152
+ :param A: float, amplitude of the best matchning sinus
153
+ :param raw: list, raw data
154
+ :param dataf_lp: list of pandas dataframes, TICOI results
155
+ :param local_var_method: str, method to be used to process the local variations
156
+ """
157
+
158
+ # Compute local variations
159
+ if local_var_method == "rolling_7d":
160
+ var = raw["vv"].rolling(window="7D", center=True).std(ddof=0).drop_duplicates().dropna().median().item()
161
+
162
+ elif local_var_method.split("_")[0] == "uniform":
163
+ period_between_dates = (
164
+ np.diff(np.sort(np.concatenate([raw["date1"], raw["date2"]]))).astype("timedelta64[D]").astype("int")
165
+ )
166
+ min_period = np.min(period_between_dates[period_between_dates > 0])
167
+ var_dates = pd.date_range(start=raw["date1"].min(), end=raw["date2"].max(), freq=f"{min_period}D")
168
+ local_var = pd.Series(index=var_dates)
169
+
170
+ if local_var_method == "uniform_7d":
171
+ for date in var_dates:
172
+ local_var[date] = raw.loc[
173
+ (raw.index > date - pd.Timedelta("3D")) & (raw.index < date + pd.Timedelta("3D")), "vv"
174
+ ].std(ddof=0)
175
+
176
+ elif local_var_method == "uniform_all":
177
+ for date in var_dates:
178
+ local_var[date] = raw.loc[(raw["date1"] < date) & (raw["date2"] > date), "vv"].std(ddof=0)
179
+
180
+ var = local_var[local_var > 0].dropna().median()
181
+
182
+ elif local_var_method == "residu":
183
+ dataf_lp.index = dataf_lp["First_date"] + (dataf_lp["Second_date"] - dataf_lp["First_date"]) // 2
184
+ dataf_lp["vv"] = np.sqrt(dataf_lp["vx"] ** 2 + dataf_lp["vy"] ** 2)
185
+ dataf_lp = dataf_lp.reindex(index=np.unique(raw.index)).interpolate().dropna()
186
+ dataf = raw[raw.index >= dataf_lp.index[0]]
187
+ dataff_vv_c = dataf["vv"] - dataf_lp["vv"]
188
+ var = dataff_vv_c.std(ddof=0)
189
+
190
+ return var
191
+
192
+
193
+ def AtoVar(A, raw, dataf_lp, local_var_method="uniform_7d"):
194
+ """
195
+ Compute Amplitude to local VARiations index, which compares the amplitude of the best matching sinus to the standard
196
+ deviation of the noise using one of the four given methods.
197
+
198
+ :param A: float, amplitude of the best matchning sinus
199
+ :param raw: list, raw data
200
+ :param dataf_lp: list of pandas dataframes, TICOI results
201
+ :param local_var_method: str, method to be used to process the local variations
202
+ """
203
+
204
+ if A == np.nan:
205
+ return np.nan
206
+
207
+ var = rolling_std(raw, dataf_lp, local_var_method)
208
+
209
+ return max(0, 1 - var / abs(A))