proadv 2.0.2__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.
Files changed (34) hide show
  1. proadv/__init__.py +0 -0
  2. proadv/filtration/__init__.py +0 -0
  3. proadv/filtration/detection/__init__.py +0 -0
  4. proadv/filtration/detection/acceleration.py +65 -0
  5. proadv/filtration/detection/bivariatekernel.py +53 -0
  6. proadv/filtration/detection/correlation.py +78 -0
  7. proadv/filtration/detection/phasespace.py +173 -0
  8. proadv/filtration/detection/poincare.py +46 -0
  9. proadv/filtration/detection/pollution.py +32 -0
  10. proadv/filtration/detection/spherical.py +181 -0
  11. proadv/filtration/detection/trivariatekernel.py +521 -0
  12. proadv/filtration/replacement/__init__.py +0 -0
  13. proadv/filtration/replacement/replacements.py +187 -0
  14. proadv/kernel/__init__.py +0 -0
  15. proadv/kernel/bivariate.py +437 -0
  16. proadv/statistics/__init__.py +0 -0
  17. proadv/statistics/descriptive.py +328 -0
  18. proadv/statistics/distributions/__init__.py +1 -0
  19. proadv/statistics/distributions/normal.py +126 -0
  20. proadv/statistics/moment.py +122 -0
  21. proadv/statistics/series.py +337 -0
  22. proadv/statistics/spread.py +119 -0
  23. proadv/tests/__init__.py +0 -0
  24. proadv/tests/statistics/__init__.py +0 -0
  25. proadv/tests/statistics/descriptive/__init__.py +0 -0
  26. proadv/tests/statistics/descriptive/test_max.py +36 -0
  27. proadv/tests/statistics/descriptive/test_mean.py +37 -0
  28. proadv/tests/statistics/descriptive/test_median.py +26 -0
  29. proadv/tests/statistics/descriptive/test_min.py +36 -0
  30. proadv/tests/statistics/descriptive/test_mode.py +34 -0
  31. proadv-2.0.2.dist-info/METADATA +167 -0
  32. proadv-2.0.2.dist-info/RECORD +34 -0
  33. proadv-2.0.2.dist-info/WHEEL +5 -0
  34. proadv-2.0.2.dist-info/top_level.txt +1 -0
proadv/__init__.py ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,65 @@
1
+ import numpy as np
2
+ from proadv.statistics.spread import std
3
+
4
+
5
+ def acceleration_thresholding(velocities, frequency, tag, gravity=980, k_gravity=1.5, k_sigma=1):
6
+ """
7
+ Detects acceleration events based on velocity data.
8
+
9
+ This function calculates acceleration events based on velocity data, considering thresholds
10
+ for acceleration magnitude and velocity deviation from mean.
11
+
12
+ Parameters
13
+ ------
14
+ velocities (array_like): Array of velocity data.
15
+ An array-like object containing velocity values.
16
+ frequency (float): Sampling frequency.
17
+ The frequency at which the velocity data is sampled,
18
+ used to calculate acceleration from velocity differences.
19
+ tag (int): Tag for acceleration direction.
20
+ An integer indicating the direction of acceleration to detect:
21
+ - 1 for positive acceleration (increasing velocity)
22
+ - 2 for negative acceleration (decreasing velocity).
23
+ gravity (float): Value of gravity.
24
+ The acceleration due to gravity, used as a reference for acceleration thresholds.
25
+ Default is 980 (m/s^2).
26
+ k_gravity (float): Threshold multiplier for gravity.
27
+ A multiplier applied to the gravity value to determine the acceleration threshold.
28
+ Default is 1.5.
29
+ k_sigma (float, optional): Threshold multiplier for standard deviation.
30
+ A multiplier applied to the standard deviation of velocities to determine velocity deviation threshold.
31
+ Default is 1.
32
+
33
+ Returns
34
+ ------
35
+ accel_indices (array_like): Indices of acceleration events.
36
+ An array containing the indices of the detected acceleration events.
37
+
38
+ Raises
39
+ ------
40
+ ValueError: If invalid tag value is provided.
41
+ Raised when the tag value is not 1 or 2.
42
+
43
+ References
44
+ ------
45
+ Goring, Derek G., and Vladimir I. Nikora.
46
+ "Despiking acoustic Doppler velocimeter data."
47
+ Journal of hydraulic engineering 128.1 (2002): 117-126.
48
+ """
49
+ velocities = np.asarray(velocities)
50
+
51
+ # Calculate velocity differences and multiply by frequency to obtain acceleration
52
+ differences = np.concatenate((np.array([0]), np.diff(velocities) * frequency))
53
+
54
+ if tag == 1:
55
+ # Detect positive acceleration events
56
+ accel_indices = np.intersect1d(np.where(differences > k_gravity * gravity)[0],
57
+ np.where(velocities > np.mean(velocities) + k_sigma * std(velocities))[0])
58
+ elif tag == 2:
59
+ # Detect negative acceleration events
60
+ accel_indices = np.intersect1d(np.where(differences < -k_gravity * gravity)[0],
61
+ np.where(velocities < np.mean(velocities) - k_sigma * std(velocities))[0])
62
+ else:
63
+ raise ValueError("Invalid tag value. Expected 1 or 2.")
64
+
65
+ return accel_indices
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+
3
+
4
+ def _cutoff(density_profile, velocity_profile, c1_threshold, c2_threshold, force_profile, peak_index, grid):
5
+ """
6
+ Find the lower and upper cutoff velocities based on specified criteria.
7
+
8
+ Parameters
9
+ ------
10
+ density_profile (array_like): Density profile of the system.
11
+ velocity_profile (array_like): Velocity profile of the system.
12
+ c1_threshold (float): Threshold ratio for force compared to peak force.
13
+ c2_threshold (float): Threshold for absolute change in force.
14
+ force_profile (array_like): Force profile of the system.
15
+ peak_index (int): Index of the peak force in the force profile.
16
+ grid (int): Grid spacing or resolution.
17
+
18
+ Returns
19
+ ------
20
+ lower_cutoff_velocity, upper_cutoff_velocity: A tuple containing the lower and upper cutoff velocities.
21
+
22
+ Note
23
+ ------
24
+ This function assumes that the input arrays (density_profile, velocity_profile, and force_profile)
25
+ are of the same length.
26
+ length.
27
+ The density profile, velocity profile, and force profile should be consistent and correspond to
28
+ each other at each index.
29
+ """
30
+
31
+ profile_length = force_profile.size
32
+ delta_force = np.append([0], np.diff(force_profile)) * grid / density_profile
33
+
34
+ # Find lower cutoff index
35
+ for i in range(peak_index - 1, 0, -1):
36
+ if force_profile[i] / force_profile[peak_index] <= c1_threshold and abs(delta_force[i]) <= c2_threshold:
37
+ lower_cutoff_index = i
38
+ break
39
+ else:
40
+ lower_cutoff_index = 1
41
+
42
+ # Find upper cutoff index
43
+ for i in range(peak_index + 1, profile_length - 1):
44
+ if force_profile[i] / force_profile[peak_index] <= c1_threshold and abs(delta_force[i]) <= c2_threshold:
45
+ upper_cutoff_index = i
46
+ break
47
+ else:
48
+ upper_cutoff_index = profile_length - 1
49
+
50
+ lower_cutoff_velocity = velocity_profile[lower_cutoff_index]
51
+ upper_cutoff_velocity = velocity_profile[upper_cutoff_index]
52
+
53
+ return lower_cutoff_velocity, upper_cutoff_velocity
@@ -0,0 +1,78 @@
1
+ import numpy as np
2
+ from proadv.filtration.detection.poincare import calculate_ab, calculate_rho
3
+ from proadv.statistics.spread import std
4
+
5
+
6
+ def calculate_parameters(up, vp, wp):
7
+ """
8
+ Calculate parameters required for velocity correlation.
9
+
10
+ Parameters
11
+ ------
12
+ up (array_like): Array of the first velocity component.
13
+ vp (array_like): Array of the second velocity component.
14
+ wp (array_like): Array of the third velocity component.
15
+
16
+ Returns
17
+ ------
18
+ lambda_ (float): Lambda value used in velocity correlation detection.
19
+ std_u (float): Standard deviation of the longitudinal velocity component.
20
+ std_v (float): Standard deviation of the transverse velocity component.
21
+ std_w (float): Standard deviation of the vertical velocity component.
22
+ """
23
+ data_size = up.size
24
+ std_u = std(up)
25
+ std_v = std(vp)
26
+ std_w = std(wp)
27
+ lambda_ = np.sqrt(2 * np.log(data_size))
28
+ return lambda_, std_u, std_v, std_w
29
+
30
+
31
+ def velocity_correlation(ui, vi, wi):
32
+ """
33
+ Detect spikes using velocity correlation filter, based on three velocity components.
34
+
35
+ Parameters
36
+ ------
37
+ ui (array_like): Array of the longitudinal velocity component.
38
+ vi (array_like): Array of the transverse velocity component.
39
+ wi (array_like): Array of the vertical velocity component.
40
+
41
+ Returns
42
+ ------
43
+ correl_indices (array_like): Indices of spikes detected by velocity correlation.
44
+
45
+ References
46
+ ------
47
+ Cea, L., J. Puertas, and L. Pena.
48
+ "Velocity measurements on highly turbulent free surface flow using ADV."
49
+ Experiments in fluids 42 (2007): 333-348.
50
+ """
51
+ from proadv.statistics.descriptive import mean
52
+ ui, vi, wi = ui - mean(ui), vi - mean(vi), wi - mean(wi)
53
+ lambda_, std_u, std_v, std_w = calculate_parameters(ui, vi, wi)
54
+
55
+ # Calculate angles between velocity components
56
+ theta1 = np.arctan(np.sum(ui * vi) / np.sum(ui ** 2))
57
+ theta2 = np.arctan(np.sum(ui * wi) / np.sum(ui ** 2))
58
+ theta3 = np.arctan(np.sum(vi * wi) / np.sum(vi ** 2))
59
+
60
+ # Calculate 'a' and 'b' coefficients for each angle
61
+ a1, b1 = calculate_ab(std_u, std_v, theta1, lambda_)
62
+ a2, b2 = calculate_ab(std_u, std_w, theta2, lambda_)
63
+ a3, b3 = calculate_ab(std_v, std_w, theta3, lambda_)
64
+
65
+ # Calculate rho values for each component pair
66
+ rho1 = calculate_rho(ui, vi, theta1, a1, b1)
67
+ rho2 = calculate_rho(ui, wi, theta2, a2, b2)
68
+ rho3 = calculate_rho(vi, wi, theta3, a3, b3)
69
+
70
+ # Find indices where rho values exceed 1 (indicating correlation)
71
+ x1 = np.nonzero(rho1 > 1)[0]
72
+ x2 = np.nonzero(rho2 > 1)[0]
73
+ x3 = np.nonzero(rho3 > 1)[0]
74
+
75
+ # Combine all detected indices and remove duplicates
76
+ correl_indices = np.sort(np.unique(np.concatenate((x1, x2, x3))))
77
+
78
+ return correl_indices
@@ -0,0 +1,173 @@
1
+ import numpy as np
2
+ from proadv.filtration.detection.poincare import calculate_rho
3
+ from proadv.statistics.spread import std
4
+
5
+
6
+ def calculate_derivatives(c):
7
+ """
8
+ Calculate time-independent first and second order derivatives of the input data.
9
+
10
+ Parameters
11
+ ------
12
+ c (array_like): Input data.
13
+
14
+ Returns
15
+ ------
16
+ dc (array_like): First derivative of the input data.
17
+ dc2 (array_like): Second derivative of the input data.
18
+ """
19
+ # Initialize arrays for first and second derivatives
20
+ dc = np.zeros_like(c)
21
+ dc2 = np.zeros_like(c)
22
+
23
+ # Calculate first derivative
24
+ for i in range(1, len(c) - 1):
25
+ dc[i] = np.around((c[i + 1] - c[i - 1]) / 2, 4)
26
+
27
+ # Calculate second derivative
28
+ for i in range(1, len(c) - 1):
29
+ dc2[i] = np.around((dc[i + 1] - dc[i - 1]) / 2, 4)
30
+
31
+ return dc, dc2
32
+
33
+
34
+ def calculate_parameters(c, dc, dc2):
35
+ """
36
+ Calculate parameters for phase-space thresholding.
37
+
38
+ Parameters
39
+ ------
40
+ c (array_like): Array of the velocity component.
41
+ dc (array_like): First derivative of the input data.
42
+ dc2 (array_like): Second derivative of the input data.
43
+
44
+ Returns
45
+ ------
46
+ std_c (float): Standard deviation of the input data.
47
+ std_dc (float): Standard deviation of the first derivative.
48
+ std_dc2 (float): Standard deviation of the second derivative.
49
+ lambda_ (float): Lambda value.
50
+ theta (float | rad): Angle between components.
51
+ a1 (float): Coefficient 'a1'.
52
+ b1 (float): Coefficient 'b1'.
53
+ a2 (float): Coefficient 'a2'.
54
+ b2 (float): Coefficient 'b2'.
55
+ a3 (float): Coefficient 'a3'.
56
+ b3 (float): Coefficient 'b3'.
57
+ """
58
+ # Calculate standard deviations
59
+ std_c = std(c)
60
+ std_dc = std(dc)
61
+ std_dc2 = std(dc2)
62
+
63
+ # Calculate lambda value
64
+ lambda_ = np.sqrt(2 * np.log(len(c)))
65
+
66
+ # Calculate theta value
67
+ theta = np.arctan(np.sum(c * dc2) / np.sum(c ** 2))
68
+
69
+ # Calculate coefficients
70
+ a1 = lambda_ * std_c
71
+ b1 = lambda_ * std_dc
72
+ a2 = lambda_ * std_dc
73
+ b2 = lambda_ * std_dc2
74
+ fact = np.cos(theta) ** 4 - np.sin(theta) ** 4
75
+ a3 = np.sqrt(a1 ** 2 * np.cos(theta) ** 2 - b2 ** 2 * np.sin(theta) ** 2) / fact
76
+ b3 = np.sqrt(b2 ** 2 * np.cos(theta) ** 2 - a1 ** 2 * np.sin(theta) ** 2) / fact
77
+ return std_c, std_dc, std_dc2, lambda_, theta, a1, b1, a2, b2, a3, b3
78
+
79
+
80
+ def phasespace_thresholding(c):
81
+ """
82
+ Detect spikes using phase-space thresholding, based on each velocity component and
83
+ their first-order and second-order derivatives.
84
+
85
+ Phase-space thresholding is a method for detecting spikes or abrupt changes in time series data
86
+ by analyzing the behavior of the data in a multidimensional space defined by the data and its derivatives.
87
+
88
+
89
+ Parameters
90
+ ------
91
+ c (array_like): Velocity component
92
+
93
+ Returns
94
+ ------
95
+ phase_indices (array_like): Array containing the indices of detected spikes.
96
+
97
+ References
98
+ ------
99
+ Goring, Derek G., and Vladimir I. Nikora.
100
+ "Despiking acoustic Doppler velocimeter data."
101
+ Journal of hydraulic engineering 128.1 (2002): 117-126.
102
+ """
103
+
104
+ # Calculate first and second order derivatives of the input data
105
+ dc, dc2 = calculate_derivatives(c)
106
+
107
+ # Calculate parameters used in phase-space thresholding
108
+ std_c, std_dc, std_dc2, lambda_, theta, a1, b1, a2, b2, a3, b3 = calculate_parameters(c, dc, dc2)
109
+
110
+ # Calculate poincare map rho values for each dimension
111
+ rho1 = calculate_rho(c, dc, 0, a1, b1)
112
+ rho2 = calculate_rho(dc, dc2, 0, a2, b2)
113
+ rho3 = calculate_rho(c, dc2, theta, a3, b3)
114
+
115
+ # Find indices where rho values exceed the threshold
116
+ x1 = np.nonzero(rho1 > 1)[0]
117
+ x2 = np.nonzero(rho2 > 1)[0]
118
+ x3 = np.nonzero(rho3 > 1)[0]
119
+
120
+ # Concatenate and sort indices to get unique spike indices
121
+ phase_indices = np.sort(np.unique(np.concatenate((x1, x2, x3))))
122
+
123
+ return phase_indices
124
+
125
+
126
+ def phasespace_thresholding_premodification(data, c1=1.5, c2=1.35):
127
+ """
128
+ Perform phase space thresholding premodification on a given data array.
129
+
130
+ Parameters
131
+ ------
132
+ data (array_like): Array of data to be processed.
133
+ c1 (float, optional): Threshold parameter for unremovable data. Default is 1.5.
134
+ c2 (float, optional): Threshold parameter for removable data. Default is 1.35.
135
+
136
+ Returns
137
+ ------
138
+ data (array_like): Processed data array with potential modifications.
139
+ unremovable_indices (array_like): Indices of unremovable data points.
140
+
141
+ Notes
142
+ ------
143
+ This function applies phase space thresholding premodification to the input data array.
144
+ Threshold parameters c1 and c2 determine which data points are considered unremovable and removable, respectively.
145
+ Unremovable data points are those within the range of -c1*theta to c1*theta, where theta is the median absolute deviation.
146
+ Removable data points are those exceeding c2*theta*sqrt(2*log(ndata)), where theta is the median absolute deviation
147
+ and ndata is the length of the data array.
148
+ Removable data points are replaced with the value of the preceding data point.
149
+
150
+ Example
151
+ ------
152
+ >>> data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
153
+ >>> phasespace_thresholding_premodification(data)
154
+ """
155
+
156
+ data_size = data.size
157
+ theta = np.median(np.absolute(data - np.median(data)))
158
+
159
+ # Calculate indices of unremovable data
160
+ unremovable_indices = np.intersect1d(np.nonzero(data >= -c1 * theta)[0], np.nonzero(data <= c1 * theta)[0])
161
+
162
+ # Calculate threshold for removable data
163
+ removable_threshold = c2 * theta * np.sqrt(2 * np.log(data_size))
164
+
165
+ # Calculate indices of removable data
166
+ removable_indices = np.intersect1d(np.nonzero(data > removable_threshold)[0],
167
+ np.nonzero(data < -removable_threshold)[0])
168
+
169
+ # Replace removable data points with the preceding data point
170
+ for i in removable_indices:
171
+ data[i] = data[i - 1]
172
+
173
+ return data, unremovable_indices
@@ -0,0 +1,46 @@
1
+ import numpy as np
2
+
3
+
4
+ def calculate_ab(std1, std2, theta, lambda_):
5
+ """
6
+ Calculate 'a' and 'b' coefficients for poincare mapping base detection algorithms.
7
+
8
+ Parameters
9
+ ------
10
+ std1 (float): Standard deviation of the first component.
11
+ std2 (float): Standard deviation of the second component.
12
+ theta (float | rad): Angle between components.
13
+ lambda_ (float): Lambda value used in algorithm.
14
+
15
+ Returns
16
+ ------
17
+ float: Coefficient 'a' used in poincare map.
18
+ float: Coefficient 'b' used in poincare map.
19
+ """
20
+ r1 = lambda_ * std1
21
+ r2 = lambda_ * std2
22
+ fact = np.cos(theta) ** 4 - np.sin(theta) ** 4
23
+ fa = (r1 ** 2 * np.cos(theta) ** 2 - r2 ** 2 * np.sin(theta) ** 2) / fact
24
+ fb = (r2 ** 2 * np.cos(theta) ** 2 - r1 ** 2 * np.sin(theta) ** 2) / fact
25
+ return np.sqrt(fa), np.sqrt(fb) if fa > 0 and fb > 0 else (1e10, 1e10)
26
+
27
+
28
+ def calculate_rho(x, y, theta, a, b):
29
+ """
30
+ Calculate rho value for poincare mapping base detection algorithms.
31
+
32
+ Parameters
33
+ ------
34
+ x (array_like): First component.
35
+ y (array_like): Second component.
36
+ theta (float | rad): Angle between components.
37
+ a (float): Coefficient 'a' used in poincare map.
38
+ b (float): Coefficient 'b' used in poincare map.
39
+
40
+ Returns
41
+ ------
42
+ rho (array_like): Rho value calculated for poincare map.
43
+ """
44
+ xp = x * np.cos(theta) + y * np.sin(theta)
45
+ yp = y * np.cos(theta) - x * np.sin(theta)
46
+ return (xp / a) ** 2 + (yp / b) ** 2
@@ -0,0 +1,32 @@
1
+ import numpy as np
2
+ from proadv.statistics.spread import std
3
+
4
+
5
+ def pollution_rate(x, xfil, k):
6
+ """
7
+ Compute the pollution rate based on the given data and threshold.
8
+
9
+ Parameters
10
+ ------
11
+ x (array_like): Input data array.
12
+ xfil (array_like): SSA analyzed data of x.
13
+ k (float): Threshold factor.
14
+
15
+ Returns
16
+ ------
17
+ pollution (float): Pollution rate as a percentage.
18
+ """
19
+
20
+ # Find positions where x exceeds the threshold
21
+ pos = np.nonzero(x > xfil + k * std(x))
22
+
23
+ # Find positions where x is below the negative threshold
24
+ neg = np.nonzero(x < -xfil - k * std(x))
25
+
26
+ # Concatenate positive and negative noises
27
+ noises = np.concatenate((pos, neg), axis=None)
28
+
29
+ # Compute pollution rate
30
+ pollution = len(noises) / len(x) * 100
31
+
32
+ return pollution
@@ -0,0 +1,181 @@
1
+ import numpy as np
2
+
3
+
4
+ def _descript(c, iteration, c_mean):
5
+ """
6
+ Calculate the mean value and center the input data.
7
+
8
+ Parameters
9
+ ------
10
+ c (array_like): Input data.
11
+ iteration (int): Loop counter.
12
+ c_mean (float): Mean value of input data.
13
+
14
+ Returns
15
+ ------
16
+ f (array_like): Centered input data.
17
+ f_mean (float): Updated mean value.
18
+ """
19
+ if iteration == 1:
20
+ f_mean = np.around(np.nanmean(c), 4)
21
+ else:
22
+ f_mean = np.around(c_mean + np.nanmean(c), 4)
23
+ f = np.around(c - np.nanmean(c), 4)
24
+ return f, f_mean
25
+
26
+
27
+ def _gradients(c):
28
+ """
29
+ Calculate the first and second order derivatives of the input data.
30
+
31
+ Parameters
32
+ ------
33
+ c (array_like): Input data.
34
+
35
+ Returns
36
+ ------
37
+ dc (array_like): First order derivative of the input data.
38
+ dc2 (array_like): Second order derivative of the input data.
39
+ """
40
+ dc = np.gradient(c)
41
+ dc2 = np.gradient(dc)
42
+ return dc, dc2
43
+
44
+
45
+ def _rotation(c, dc, dc2, theta):
46
+ """
47
+ Rotate the input data based on the specified angle theta.
48
+
49
+ Parameters
50
+ ------
51
+ c (array_like): Input data.
52
+ dc (array_like): First order derivative of the input data.
53
+ dc2 (array_like): Second order derivative of the input data.
54
+
55
+ Returns
56
+ ------
57
+ x (array_like): Rotated X-axis data.
58
+ y (array_like): Rotated Y-axis data.
59
+ z (array_like): Rotated Z-axis data.
60
+ """
61
+ if theta == 0:
62
+ x = c.copy()
63
+ y = dc.copy()
64
+ z = dc2.copy()
65
+ else:
66
+ rotation_matrix = np.around(np.array([
67
+ [np.cos(theta), 0, np.sin(theta)],
68
+ [0, 1, 0],
69
+ [-np.sin(theta), 0, np.cos(theta)]
70
+ ]), 4)
71
+ x = np.around(c * rotation_matrix[0, 0] + dc * rotation_matrix[0, 1] + dc2 * rotation_matrix[0, 2], 4)
72
+ y = np.around(c * rotation_matrix[1, 0] + dc * rotation_matrix[1, 1] + dc2 * rotation_matrix[1, 2], 4)
73
+ z = np.around(c * rotation_matrix[2, 0] + dc * rotation_matrix[2, 1] + dc2 * rotation_matrix[2, 2], 4)
74
+ return x, y, z
75
+
76
+
77
+ def _parameters(x, y, z):
78
+ """
79
+ Calculate parameters for phase-space thresholding in spherical coordinates.
80
+
81
+ Parameters
82
+ ------
83
+ x (array_like): Rotated X-axis data.
84
+ y (array_like): Rotated Y-axis data.
85
+ z (array_like): Rotated Z-axis data.
86
+
87
+ Returns
88
+ ------
89
+ a (float): Coefficient 'a'.
90
+ b (float): Coefficient 'b'.
91
+ c (float): Coefficient 'c'.
92
+ """
93
+ lambda_ = np.around(np.sqrt(2 * np.log(x.size)), 4)
94
+ a = np.around(lambda_ * np.nanstd(x), 4)
95
+ b = np.around(lambda_ * np.nanstd(y), 4)
96
+ c = np.around(lambda_ * np.nanstd(z), 4)
97
+ return a, b, c
98
+
99
+
100
+ def _spike_indices(x, y, z, a, b, c):
101
+ """
102
+ Calculate spike indices based on the input data and coefficients.
103
+
104
+ Parameters
105
+ ------
106
+ x (array_like): Rotated X-axis data.
107
+ y (array_like): Rotated Y-axis data.
108
+ z (array_like): Rotated Z-axis data.
109
+ a (float): Coefficient 'a'.
110
+ b (float): Coefficient 'a'.
111
+ c (float): Coefficient 'a'.
112
+
113
+ Returns
114
+ ------
115
+ spike_indices (array_like): Indices of detected spikes.
116
+ """
117
+ xp, yp, zp, ip = [], [], [], []
118
+ for i in range(x.size):
119
+ x1 = x[i]
120
+ y1 = y[i]
121
+ z1 = z[i]
122
+ x2 = np.around(a * b * c * x1 / np.sqrt((a * c * y1) ** 2 + b ** 2 * (c ** 2 * x1 ** 2 + a ** 2 * z1 ** 2)), 4)
123
+ y2 = np.around(a * b * c * y1 / np.sqrt((a * c * y1) ** 2 + b ** 2 * (c ** 2 * x1 ** 2 + a ** 2 * z1 ** 2)), 4)
124
+ zt = np.around(c ** 2 * (1 - (x2 / a) ** 2 - (y2 / b) ** 2), 4)
125
+ if z1 < 0:
126
+ z2 = -np.around(np.sqrt(zt), 4)
127
+ elif z1 > 0:
128
+ z2 = np.around(np.sqrt(zt), 4)
129
+ else:
130
+ z2 = 0
131
+ dis = (x2 ** 2 + y2 ** 2 + z2 ** 2) - (x1 ** 2 + y1 ** 2 + z1 ** 2)
132
+ if dis < 0:
133
+ ip.append(i)
134
+ xp.append(x[i])
135
+ yp.append(y[i])
136
+ zp.append(z[i])
137
+ spike_indices = np.array(ip, dtype=np.int64)
138
+ return spike_indices
139
+
140
+
141
+ def spherical_phasespace_thresholding(c, iteration, c_mean):
142
+ """
143
+ Detect spikes using three-dimensional phase-space thresholding, based on each velocity component and
144
+ their first-order and second-order derivatives.
145
+
146
+ Parameters
147
+ ------
148
+ c (array_like): Velocity component
149
+ iteration (int): Loop counter.
150
+ c_mean (float): Mean of the velocity component
151
+
152
+ Returns
153
+ ------
154
+ spherical_indices (array_like): Array containing the indices of detected spikes.
155
+
156
+ References
157
+ ------
158
+ Wahl, Tony L.
159
+ "Discussion of “Despiking acoustic doppler velocimeter data” by Derek G. Goring and Vladimir I. Nikora."
160
+ Journal of Hydraulic Engineering 129, no. 6 (2003): 484-487.
161
+ """
162
+
163
+ # Calculate mean and center the data
164
+ c, c_mean = _descript(c, iteration, c_mean)
165
+
166
+ # Calculate gradients
167
+ dc, dc2 = _gradients(c)
168
+
169
+ # Calculaterotation angle
170
+ theta = np.around(np.arctan2(np.sum(c * dc), np.sum(dc2 ** 2)), 4)
171
+
172
+ # Rotate data
173
+ x, y, z = _rotation(c, dc, dc2, theta)
174
+
175
+ # Calculate parameters
176
+ a, b, c = _parameters(x, y, z)
177
+
178
+ # Calculate spike indices
179
+ spherical_indices = _spike_indices(x, y, z, a, b, c)
180
+
181
+ return spherical_indices