Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
metrics.py
Go to the documentation of this file.
1import numpy as np
2import sys
3from scipy.sparse.csgraph import minimum_spanning_tree
4import rclpy
5import rclpy.logging
6
7
8def get_average_difference(output: np.array, expected: np.array) -> float:
9 """!
10 Computes the average difference between an output output and the expected values.
11
12 Args:
13 output (np.array): Empirical Output.
14 expected (np.array): Expected output.
15
16 Returns:
17 float: Average difference between empirical and expected outputs.
18 """
19
20 if len(output) == 0:
21 return float("inf")
22
23 if len(expected) == 0:
24 raise ValueError(
25 "No ground truth cones provided for computing average difference."
26 )
27
28 # Step 1 & 2: Compute the differences using broadcasting and vectorization
29 differences = np.linalg.norm(
30 output[:, np.newaxis, :] - expected[np.newaxis, :, :], axis=2
31 )
32
33 # Step 3: Find the minimum squared difference for each output_value and sum them up
34 min_differences_sum: float = np.min(differences, axis=1).sum()
35
36 # Step 4: Compute MSE
37 me: float = min_differences_sum / len(output)
38
39 return me
40
41
42def get_false_positives(
43 output: np.ndarray, expected: np.ndarray, threshold: float
44) -> int:
45 """!
46 Computes the number of false positives in the output compared to the expected values.
47
48 Args:
49 output (np.ndarray): Empirical Output.
50 expected (np.ndarray): Expected output.
51 threshold (float): Distance threshold to consider values as matching.
52
53 Returns:
54 int: Number of false positives.
55 """
56 if len(output) == 0:
57 return 0
58
59 if len(expected) == 0:
60 raise ValueError(
61 "No ground truth values provided for computing false positives."
62 )
63
64 differences = np.linalg.norm(
65 output[:, np.newaxis, :] - expected[np.newaxis, :, :], axis=-1
66 )
67
68 matched_output = np.full(len(output), False) # Track which outputs are matched
69 matched_expected = np.full(len(expected), False) # Track expected matches
70
71 for i in range(len(output)):
72 for j in range(len(expected)):
73 if not matched_expected[j] and differences[i, j] < threshold:
74 matched_output[i] = True
75 matched_expected[j] = True
76 break
77
78 true_positives = np.sum(matched_output)
79
80 return max(0, len(output) - true_positives)
81
82
83def get_mean_squared_difference(output: np.ndarray, expected: np.ndarray) -> float:
84 """!
85 Computes the mean squared difference between an output output and the expected values.
86
87 Args:
88 output (list): Empirical Output.
89 expected (list): Expected output.
90
91 Returns:
92 float: Mean squared difference.
93 """
94 if output is None:
95 raise ValueError("No perception output provided.")
96 if expected is None:
97 raise ValueError("No ground truth cones provided.")
98
99 # Step 1 & 2: Compute the squared differences using broadcasting and vectorization
100 differences = (
101 np.linalg.norm(output[:, np.newaxis, :] - expected[np.newaxis, :, :], axis=2)
102 ** 2
103 )
104
105 # Step 3: Find the minimum squared difference for each output_value and sum them up
106 min_differences_sum = np.min(differences, axis=1).sum()
107
108 # Step 4: Compute MSE
109 mse = min_differences_sum / len(output)
110
111 return mse
112
113
114def compute_distance(cone1: np.array, cone2: np.array) -> float:
115 """!
116 Compute the Euclidean distance between two cones.
117 Args:
118 cone1 (np.array): The coordinates of the first cone.
119 cone2 (np.array): The coordinates of the second cone.
120 Returns:
121 float: The Euclidean distance between the two cones.
122 """
123
124 return np.linalg.norm(cone1 - cone2)
125
126
127def build_adjacency_matrix(cones: np.array) -> np.array:
128 """
129 Build an adjacency matrix based on the distances between cones.
130
131 Args:
132 cones (np.array): An array containing the coordinates of cones.
133
134 Returns:
135 np.array: The adjacency matrix representing the distances between cones.
136 """
137
138 num_cones = cones.shape[0]
139
140 if num_cones == 0:
141 return np.array([])
142
143 differences = cones[:, np.newaxis] - cones[np.newaxis, :]
144 distances = np.linalg.norm(differences, axis=-1)
145
146 np.fill_diagonal(distances, 0.0)
147
148 return distances
149
150
151def get_duplicates(output: np.array, threshold: float) -> int:
152 """
153 Receives a set of cones and identifies the possible duplicates.
154
155 Args:
156 output (np.array): The set of cones.
157 threshold (float): The threshold value to consider cones different or duplicates.
158
159 Returns:
160 int: The number of possible duplicates.
161
162 """
163
164 adjacency_matrix = build_adjacency_matrix(output)
165 num_duplicates = np.sum(np.tril(adjacency_matrix < threshold, k=-1))
166
167 return num_duplicates
168
169
170def get_inter_cones_distance(perception_output: np.array) -> float:
171 """!
172 Computes the average distance between pairs of perceived cones using Minimum Spanning Tree Prim's algorithm.
173
174 Args:
175 perception_output (np.array): List of perceived cones, where each cone is represented as a numpy array.
176
177 Returns:
178 float: Average distance between pairs of perceived cones.
179 """
180
181 adjacency_matrix: np.array = build_adjacency_matrix(perception_output)
182
183 adjacency_matrix: list[np.array] = [
184 adjacency_matrix[i] for i in range(len(adjacency_matrix))
185 ]
186
187 if len(adjacency_matrix) == 0:
188 return 0
189
190 mst = minimum_spanning_tree(adjacency_matrix)
191 mst_sum = mst.sum()
192
193 num_pairs = np.count_nonzero(mst.toarray().astype(float))
194
195 if num_pairs == 0:
196 return 0.0
197 else:
198 average_distance = mst_sum / num_pairs
199 return float(average_distance)
200
201
202def compute_closest_distances(arr1: np.ndarray, arr2: np.ndarray) -> np.ndarray:
203 """!
204 Computes the distance between each element in arr2 and the closest element in arr1.
205
206 Args:
207 arr1 (np.ndarray): First array of positions.
208 arr2 (np.ndarray): Second array of positions.
209
210 Returns:
211 np.ndarray: Array of distances between each element in arr2 and the closest element in arr1.
212 """
213 # Extract the x and y positions
214 arr1_xy = arr1[:, :2]
215 arr2_xy = arr2[:, :2]
216
217 # Calculate the squared Euclidean distances
218 distances = np.linalg.norm(
219 arr2_xy[:, np.newaxis, :] - arr1_xy[np.newaxis, :, :], axis=2
220 )
221
222 # Find the minimum distances for each element in arr2
223 closest_distances = np.min(distances, axis=1)
224
225 return closest_distances
226
227
228def get_average_error(values: np.array) -> float:
229 """!
230 Computes the average of a list of values.
231
232 Args:
233 values (np.array): List of values.
234
235 Returns:
236 float: Average of the values.
237 """
238 if len(values) == 0:
239 return 0.0
240
241 return np.mean(values)
242
243
244def get_mean_squared_error(values: np.array) -> float:
245 """!
246 Computes the mean squared value of a list of values.
247
248 Args:
249 values (np.array): List of values.
250
251 Returns:
252 float: Mean squared value of the values.
253 """
254 if len(values) == 0:
255 return 0.0
256
257 return np.mean(values**2)
258
259
260def get_root_mean_squared_error(values: np.array) -> float:
261 """!
262 Computes the root mean squared error of a list of values.
263
264 Args:
265 values (np.array): List of values.
266
267 Returns:
268 float: Root mean squared error of the values.
269 """
270 if len(values) == 0:
271 return 0.0
272
273 return np.sqrt(np.mean(values**2))
float get_root_mean_squared_error(np.array values)
Computes the root mean squared error of a list of values.
Definition metrics.py:260