STM32N6 NPU Deployment — Politecnico di Milano  1.0
Documentation for Neural Network Deployment on STM32N6 NPU - Politecnico di Milano 2024-2025
metrics.py
Go to the documentation of this file.
1 # /*---------------------------------------------------------------------------------------------
2 # * Copyright (c) 2024 STMicroelectronics.
3 # * All rights reserved.
4 # *
5 # * This software is licensed under terms that can be found in the LICENSE file in
6 # * the root directory of this software component.
7 # * If no LICENSE file comes with this software, it is provided AS-IS.
8 # *--------------------------------------------------------------------------------------------*/
9 
10 
19 Authors: Giacomo Colosio, Sebastiano Colosio, Patrizio Acquadro, Tito Nicola Drugman
20 #
21 # @copyright Copyright (c) 2023-2024 STMicroelectronics. All rights reserved.
22 
23 
24 import numpy as np
25 import tensorflow as tf
26 import matplotlib.pyplot as plt
27 from typing import Optional
28 
29 from src.postprocessing import heatmaps_spe_postprocess
30 
31 
32 def _oks_matrix(kpt0:tf.Tensor, kpt1:tf.Tensor, area:tf.Tensor, stddev:tf.Tensor, eps:Optional[float] = 1e-7):
33  '''
34  Calculate OKS (object keypoint similarities) matrix
35 
36  Args
37  kpt0 (tf.Tensor): shape (batch, N, nb_kpts, 3) FLOAT32 representing ground truth keypoints.
38  kpt1 (tf.Tensor): shape (batch, M, nb_kpts, 3) FLOAT32 representing predicted keypoints.
39  area (tf.Tensor): shape (batch, N) FLOAT32 representing areas of the poses of the gt kpts.
40  stddev (tf.Tensor): shape (nb_kpts, ) FLOAT32 representing the normalized keypoints standard deviation
41  eps Optional[float]: shape (1,) FLOAT32 value used to avoid division by zero
42 
43  Returns:
44  oks (tf.Tensor): shape (batch, N, M) representing the object keypoint similarities.
45  '''
46 
47  area *= 0.53 # `0.53` is from https://github.com/jin-s13/xtcocoapi/blob/master/xtcocotools/cocoeval.py#L384
48 
49  X_2 = tf.square(kpt0[:, :, None, :, 0] - kpt1[:, None, :, :, 0]) # (batch,N,M,nb_kpts) distances between all keypoints on the x axis
50  Y_2 = tf.square(kpt0[:, :, None, :, 1] - kpt1[:, None, :, :, 1]) # (batch,N,M,nb_kpts) distances between all keypoints on the y axis
51 
52  mask = kpt0[..., 2] # (batch,N,nb_kpts) visible keypoints
53 
54  distances_2 = X_2 + Y_2 # (batch,N,M,nb_kpts) distances between all keypoints
55 
56  z = -distances_2 / (tf.square(2*stddev[None, None, None, :]) * (area[:, :, None, None] + eps) * 2) # (batch,N,M,nb_kpts)
57 
58  oks = tf.math.reduce_sum(tf.math.exp(z) * mask[:, :, None, :], axis=-1) / (tf.math.reduce_sum(mask, axis=-1)[:, :, None] + eps) # (batch,N,M) OKS formula https://cocodataset.org/#keypoints-eval
59 
60  return oks
61 
62 def _pose_area_calculation(tensor:tf.Tensor):
63  '''
64  Calculate the area of a pose
65 
66  Args
67  tensor (tf.Tensor): shape (batch, N, 2) FLOAT32 bounding box [w h] values
68 
69  Returns:
70  area (tf.Tensor): shape (batch, N) FLOAT32 W*H area of the poses
71  '''
72 
73  area = tensor[...,0] * tensor[...,1] # w * h
74 
75  return area
76 
77 def _matching_predictions(tensor:tf.Tensor, thres:tf.Tensor):
78  '''
79  Matching of the predictions with the ground truths
80 
81  Args
82  tensor (tf.Tensor): shape (batch, N, M) FLOAT32 representing the object keypoint similarities.
83  thres (tf.Tensor): shape (thres, ) FLOAT32 thresholds for the OKS
84 
85  Returns:
86  matching (tf.Tensor): shape (batch, M, thres) FLOAT32 0 and 1's representing the matching of the preds with a gt for every thresholds.
87  '''
88 
89  batch,N,M = tf.shape(tensor)
90 
91  rtensor = tf.reshape(tensor,[-1,N*M]) # (batch, N*M)
92 
93  # sorting this tensor from the best score to the worst per batch
94  stensor = tf.argsort(rtensor)[:,::-1] # (batch, N*M) INT32
95 
96  # creation of tensors used in the tensor_scatter_nd_update function
97  z = tf.zeros(tf.shape(stensor),tf.int32) # (batch, N*M) INT32
98 
99  range_t = tf.tile(tf.range(batch)[:,None],[1,N*M]) # (batch, N*M) INT32
100  range_t = tf.reshape(range_t,-1) # (batch*N*M) INT32
101 
102  fstensor = tf.reshape(stensor,-1) # (batch*N*M) INT32
103  fstensor = tf.stack([range_t,fstensor],-1) # (batch*N*M,2) INT32
104 
105  updates = tf.range(batch*N*M) # (batch*N*M) INT32
106 
107  # this function act as the inverse function of argsort applied on the sorted tensor
108  stensor = tf.tensor_scatter_nd_update(tensor=z,
109  indices=fstensor,
110  updates=updates) # (batch, N*M) INT32
111  stensor -= tf.range(batch)[:,None]*N*M
112 
113  nm = tf.cast(N*M,tf.float32)
114 
115  # invert the order of the sort to have at 0 the best value of oks
116  stensor = (nm-1.) - tf.cast(stensor,tf.float32) # (batch, N, M) FLOAT32
117  rstensor = tf.reshape(stensor,[-1,N,M]) # (batch, N, M) FLOAT32
118 
119  # find the argmax of this tensor and create masks
120  arstensor = tf.argmax(rstensor,axis=1) # (batch, M) INT64
121  oarst = tf.one_hot(arstensor,N,axis=1) # (batch, N, M) FLOAT32
122 
123 
124  # -1's are not selected
125  rstensor = rstensor*oarst - (1-oarst) # (batch, N, M) FLOAT32
126 
127  eqzz = tf.reduce_sum(oarst,axis=-1) # (batch, N,) FLOAT32
128  eqzz = tf.cast(eqzz!=0,tf.float32) # (batch, N,) FLOAT32
129 
130  brstensor = tf.argmax(rstensor,axis=-1) # (batch, N) INT64
131  obrst = tf.one_hot(brstensor,M,axis=-1) # (batch, N, M) FLOAT32
132  obrst *= eqzz[:,:,None] # (batch, N, M) FLOAT32
133 
134  matching = obrst # (batch, N, M) FLOAT32
135 
136  oks_v = tf.reduce_max(tensor * matching,axis=1) # (batch, M) FLOAT32
137 
138  t_matching = oks_v > thres[:,None,None] # (thres, batch, M) BOOL
139  t_matching = tf.cast(t_matching,tf.float32) # (thres, batch, M) FLOAT32
140  t_matching = tf.transpose(t_matching,[1,2,0]) # (batch, M, thres) FLOAT32
141 
142  return t_matching
143 
144 def single_pose_oks(y_true:tf.Tensor, y_pred:tf.Tensor):
145  '''
146  Compute the OKS (object keypoint similarities) metric for single pose estimation
147 
148  Args
149  y_true (tf.Tensor): shape (batch, 1, 5+nb_kpts*3) FLOAT32 representing ground truth keypoints.
150  y_pred (tf.Tensor): shape (batch, 1, nb_kpts*3) FLOAT32 representing predicted keypoints.
151 
152  Returns:
153  spe_oks (tf.Tensor): shape (batch,) representing the object keypoint similarities.
154  '''
155 
156  shg = tf.shape(y_true)
157  shp = tf.shape(y_pred)
158 
159  nb_kpts = shp[2]//3
160 
161  area = _pose_area_calculation(y_true[:,:,3:5]) # (batch, 1)
162 
163  def f1(): return tf.constant([0.026,0.025,0.025,0.035,0.035,0.079,0.079,0.072,0.072,0.062,0.062,0.107,0.107,0.087,0.087,0.089,0.089],tf.float32)
164  def f2(): return tf.constant([0.035,0.079,0.079,0.072,0.072,0.062,0.062,0.107,0.107,0.087,0.087,0.089,0.089],tf.float32)
165  def f3(): return tf.zeros(nb_kpts,tf.float32)
166 
167  stddev = tf.case([(nb_kpts == 17, f1), (nb_kpts == 13, f2)], default=f3, exclusive=True)
168 
169  gt = y_true[:,:,5:] # shape (batch, 1, nb_kpts*3)
170  gt = tf.reshape(gt, [shg[0],shg[1],nb_kpts,3]) # shape (batch, 1, nb_kpts, 3)
171  pred = tf.reshape(y_pred,[shp[0],shp[1],nb_kpts,3]) # shape (batch, 1, nb_kpts, 3)
172 
173  oks_m = _oks_matrix(gt,pred,area,stddev)
174 
175  return oks_m[:,0,0] # shape (batch,)
176 
177 def single_pose_heatmaps_oks(y_true:tf.Tensor, y_pred:tf.Tensor):
178  '''
179  Compute the OKS (object keypoint similarities) metric for single pose estimation heatmaps
180 
181  Args
182  y_true (tf.Tensor): shape (batch, 1, 5+nb_kpts*3) FLOAT32 representing ground truth keypoints.
183  y_pred (tf.Tensor): shape (batch, res, res, keypoints) FLOAT32 representing predicted heatmaps.
184 
185  Returns:
186  spe_oks (tf.Tensor): shape (batch,) representing the object keypoint similarities.
187  '''
188  y_pred = heatmaps_spe_postprocess(y_pred)
189  return single_pose_oks(y_true, y_pred)
190 
191 def multi_pose_oks_mAP(y_true:tf.Tensor, y_pred:tf.Tensor):
192  '''
193  Compute the OKS mAP (object keypoint similarities - mean Average Precision) metric for multi pose estimation
194 
195  Args
196  y_true (tf.Tensor): shape (batch, N, 5+nb_kpts*3) FLOAT32 representing ground truth keypoints.
197  y_pred (tf.Tensor): shape (batch, M, 5+nb_kpts*3) FLOAT32 representing predicted keypoints.
198 
199  Returns:
200  tp (tf.Tensor): shape (batch*M,thresh) FLOAT32 0's and 1's representing the true positives
201  conf (tf.Tensor): shape (batch*M,) FLOAT32 box confidences of the detections
202  nb_gt (tf.Tensor): shape (1,) FLOAT32 number ground truths
203  maskpad (tf.Tensor): shape (batch*M,) FLOAT32 mask for the padding of predictions
204  '''
205 
206  sh = tf.shape(y_pred[:,:,5:])
207 
208  nb_kpts = sh[2]//3
209 
210  area = _pose_area_calculation(y_true[:,:,3:5]) # shape : (batch, N) FLOAT32 calculate area for the pose
211  gtT = tf.reduce_sum(y_true[:,:,5:],-1) # shape : (batch, M) FLOAT32 sum of all prediction values for each prediction
212  preT = tf.reduce_sum(y_pred,-1) # shape : (batch, M) FLOAT32 sum of all prediction values for each prediction
213  maskpad = tf.cast(preT>0,tf.float32) # shape : (batch, M) FLOAT32 see if a prediction if truly a prediction or just a padding
214  maskpad = tf.reshape(maskpad,[-1]) # shape : (batch*M,) FLOAT32
215 
216  stddev = tf.constant([0.026,0.025,0.025,0.035,0.035,0.079,0.079,0.072,0.072,0.062,0.062,0.107,0.107,0.087,0.087,0.089,0.089],tf.float32)
217  thres = tf.constant([0.5 , 0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9, 0.95],tf.float32)
218 
219  gt = y_true[:,:,5:] # shape (batch, N, nb_kpts*3)
220  pred = y_pred[:,:,5:] # shape (batch, M, nb_kpts*3)
221  gt = tf.reshape(gt, [sh[0],-1,nb_kpts,3]) # shape (batch, N, nb_kpts, 3)
222  pred = tf.reshape(pred,[sh[0],-1,nb_kpts,3]) # shape (batch, M, nb_kpts, 3)
223 
224  conf = y_pred[:,:,4] # shape (batch, M)
225  conf = tf.reshape(conf,[-1]) # shape (batch*M,)
226 
227  oks_m = _oks_matrix(gt,pred,area,stddev) # (batch, N, M)
228 
229  mtp = _matching_predictions(oks_m,thres) # (batch, M, thres)
230 
231  tp = tf.reshape(mtp,[sh[0]*sh[1],-1]) # (batch*M, thres)
232 
233  nb_gt = tf.math.count_nonzero(gtT) # (batch, ) INT64
234  nb_gt = tf.reduce_sum(nb_gt) # (1, ) INT64
235  nb_gt = tf.cast(nb_gt, tf.float32) # (1, ) FLOAT32
236 
237  return tp, conf, nb_gt, maskpad # (batch*M,thresh), (batch*M,), (1,), (batch*M,)
238 
239 def _precision_recall(tp:tf.Tensor, conf:tf.Tensor, nb_gt:tf.Tensor, maskpad:tf.Tensor, eps:Optional[float] = 1e-7):
240  '''
241  Compute precision and recall for all thresholds
242 
243  Args
244  tp (tf.Tensor): shape (batch*M,thresh) FLOAT32 0's and 1's representing the true positives
245  conf (tf.Tensor): shape (batch*M,) FLOAT32 box confidences of the detections
246  nb_gt (tf.Tensor): shape (1,) FLOAT32 number ground truths
247  maskpad (tf.Tensor): shape (batch*M,) FLOAT32 mask for the padding of predictions
248  eps Optional[float]: shape (1,) FLOAT32 value used to avoid division by zero
249 
250  Returns:
251  precision (tf.Tensor): shape (thres, ) FLOAT32 precision values for all the thresholds
252  recall (tf.Tensor): shape (thres, ) FLOAT32 recall values for all the thresholds
253  '''
254 
255  conf_sort = tf.argsort(conf)[::-1] # (batch*M,) sort the confidence scores of the boxes by descending order
256 
257  ntp = tf.gather(tp,conf_sort) # (batch*M,thresh) reorder the axis 0 with sorted confidences
258 
259  nmaskpad = tf.gather(maskpad,conf_sort) # (batch*M,) reorder with sorted confidences
260 
261  TP = tf.cumsum(ntp) # (batch*M,thresh) cumulative summation on the axis 0
262  FP = tf.cumsum((1-ntp)*nmaskpad[:,None]) # (batch*M,thresh) cumulative summation on the axis 0
263 
264  precision = TP / (TP + FP + eps) # (batch*M,thresh) precision calculation
265  recall = TP / (nb_gt + eps) # (batch*M,thresh) recall calculation
266 
267  return precision, recall
268 
269 def _auc(precision:tf.Tensor, recall:tf.Tensor):
270  '''
271  Compute the Area Under the Curve (AUC) that gives the mAP value
272 
273  Args
274  precision (tf.Tensor): shape (thres, ) FLOAT32 precision values for all the thresholds
275  recall (tf.Tensor): shape (thres, ) FLOAT32 recall values for all the thresholds
276 
277  Returns:
278  mAP (tf.Tensor): shape (1, ) FLOAT32 mean Average Precision value
279  '''
280 
281  # Append sentinel values to beginning and end
282  mrec = np.concatenate(([0.0], recall, [1.0]))
283  mpre = np.concatenate(([1.0], precision, [0.0]))
284 
285  # Compute the precision envelope
286  mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
287 
288  _,i_mrec = np.unique(mrec,return_index=True)
289 
290  mrec = mrec[i_mrec]
291  mpre = mpre[i_mrec]
292 
293  # Integrate area under curve
294  x = np.linspace(0, 1, 101) # 101-point interpolation (COCO)
295  interpolation = np.interp(x, mrec, mpre)
296  ap = np.trapz(interpolation, x) # integrate
297 
298  return ap, mpre, mrec
299 
300 def compute_ap(tp:tf.Tensor, conf:tf.Tensor, nb_gt:tf.Tensor, maskpad:tf.Tensor, plot_metrics:bool):
301  '''
302  Compute the AP (Average Precision) for each threshold values
303 
304  Args
305  tp (tf.Tensor): shape (batch*M,thresh) FLOAT32 0's and 1's representing the true positives
306  conf (tf.Tensor): shape (batch*M,) FLOAT32 box confidences of the detections
307  nb_gt (tf.Tensor): shape (1,) FLOAT32 number ground truths
308  maskpad (tf.Tensor): shape (batch*M,) FLOAT32 mask for the padding of predictions
309  plot_metrics (bool): shape (1,) BOOL if true the precision/recall curve will be drawn
310 
311  Returns:
312  mAPs (tf.Tensor): shape (thresh, ) FLOAT32 mean Average Precision values
313  '''
314 
315  precision,recall = _precision_recall(tp, conf, nb_gt, maskpad)
316 
317  sp = tf.shape(precision)
318 
319  mAPs = []
320 
321  for i in range(sp[1]):
322  ac = _auc(precision[:,i],recall[:,i])
323  mAPs.append(ac[0])
324 
325  if plot_metrics:
326  plt.plot(recall[:,0],precision[:,0])
327  plt.show()
328 
329  return mAPs
def _auc(tf.Tensor precision, tf.Tensor recall)
Definition: metrics.py:269
def compute_ap(tf.Tensor tp, tf.Tensor conf, tf.Tensor nb_gt, tf.Tensor maskpad, bool plot_metrics)
Definition: metrics.py:300
def _precision_recall(tf.Tensor tp, tf.Tensor conf, tf.Tensor nb_gt, tf.Tensor maskpad, Optional[float] eps=1e-7)
Definition: metrics.py:239
def multi_pose_oks_mAP(tf.Tensor y_true, tf.Tensor y_pred)
Definition: metrics.py:191
def single_pose_heatmaps_oks(tf.Tensor y_true, tf.Tensor y_pred)
Definition: metrics.py:177
def _pose_area_calculation(tf.Tensor tensor)
Definition: metrics.py:62
def _oks_matrix(tf.Tensor kpt0, tf.Tensor kpt1, tf.Tensor area, tf.Tensor stddev, Optional[float] eps=1e-7)
Definition: metrics.py:32
def single_pose_oks(tf.Tensor y_true, tf.Tensor y_pred)
Definition: metrics.py:144
def _matching_predictions(tf.Tensor tensor, tf.Tensor thres)
Definition: metrics.py:77
def heatmaps_spe_postprocess(tf.Tensor tensor)
Definition: postprocess.py:30