blob: 853ab089d0b699d3b417f812e71d18d66bcf617b [file] [log] [blame]
alexander3c798932021-03-26 21:42:19 +00001/*
Richard Burtoned35a6f2022-02-14 11:55:35 +00002 * Copyright (c) 2021-2022 Arm Limited. All rights reserved.
alexander3c798932021-03-26 21:42:19 +00003 * SPDX-License-Identifier: Apache-2.0
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17#include "UseCaseHandler.hpp"
18
19#include "AdModel.hpp"
20#include "InputFiles.hpp"
21#include "Classifier.hpp"
22#include "hal.h"
23#include "AdMelSpectrogram.hpp"
24#include "AudioUtils.hpp"
Richard Burtoned35a6f2022-02-14 11:55:35 +000025#include "ImageUtils.hpp"
alexander3c798932021-03-26 21:42:19 +000026#include "UseCaseCommonUtils.hpp"
27#include "AdPostProcessing.hpp"
alexander31ae9f02022-02-10 16:15:54 +000028#include "log_macros.h"
alexander3c798932021-03-26 21:42:19 +000029
30namespace arm {
31namespace app {
32
33 /**
alexander3c798932021-03-26 21:42:19 +000034 * @brief Presents inference results using the data presentation
35 * object.
36 * @param[in] platform reference to the hal platform object
37 * @param[in] result average sum of classification results
Isabella Gottardi56ee6202021-05-12 08:27:15 +010038 * @param[in] threshold if larger than this value we have an anomaly
alexander3c798932021-03-26 21:42:19 +000039 * @return true if successful, false otherwise
40 **/
alexanderc350cdc2021-04-29 20:36:09 +010041 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold);
alexander3c798932021-03-26 21:42:19 +000042
43 /**
44 * @brief Returns a function to perform feature calculation and populates input tensor data with
45 * MelSpe data.
46 *
47 * Input tensor data type check is performed to choose correct MFCC feature data type.
48 * If tensor has an integer data type then original features are quantised.
49 *
50 * Warning: mfcc calculator provided as input must have the same life scope as returned function.
51 *
Isabella Gottardi56ee6202021-05-12 08:27:15 +010052 * @param[in] melSpec MFCC feature calculator.
53 * @param[in,out] inputTensor Input tensor pointer to store calculated features.
54 * @param[in] cacheSize Size of the feture vectors cache (number of feature vectors).
55 * @param[in] trainingMean Training mean.
alexander3c798932021-03-26 21:42:19 +000056 * @return function function to be called providing audio sample and sliding window index.
57 */
58 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
59 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec,
60 TfLiteTensor* inputTensor,
61 size_t cacheSize,
62 float trainingMean);
63
64 /* Vibration classification handler */
65 bool ClassifyVibrationHandler(ApplicationContext& ctx, uint32_t clipIndex, bool runAll)
66 {
67 auto& platform = ctx.Get<hal_platform&>("platform");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010068 auto& profiler = ctx.Get<Profiler&>("profiler");
alexander3c798932021-03-26 21:42:19 +000069
70 constexpr uint32_t dataPsnTxtInfStartX = 20;
71 constexpr uint32_t dataPsnTxtInfStartY = 40;
72
alexander3c798932021-03-26 21:42:19 +000073 auto& model = ctx.Get<Model&>("model");
74
75 /* If the request has a valid size, set the audio index */
76 if (clipIndex < NUMBER_OF_FILES) {
Éanna Ó Catháin8f958872021-09-15 09:32:30 +010077 if (!SetAppCtxIfmIdx(ctx, clipIndex,"clipIndex")) {
alexander3c798932021-03-26 21:42:19 +000078 return false;
79 }
80 }
81 if (!model.IsInited()) {
82 printf_err("Model is not initialised! Terminating processing.\n");
83 return false;
84 }
85
86 const auto frameLength = ctx.Get<int>("frameLength");
87 const auto frameStride = ctx.Get<int>("frameStride");
88 const auto scoreThreshold = ctx.Get<float>("scoreThreshold");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010089 const auto trainingMean = ctx.Get<float>("trainingMean");
alexander3c798932021-03-26 21:42:19 +000090 auto startClipIdx = ctx.Get<uint32_t>("clipIndex");
91
92 TfLiteTensor* outputTensor = model.GetOutputTensor(0);
93 TfLiteTensor* inputTensor = model.GetInputTensor(0);
94
95 if (!inputTensor->dims) {
96 printf_err("Invalid input tensor dims\n");
97 return false;
98 }
99
100 TfLiteIntArray* inputShape = model.GetInputShape(0);
101 const uint32_t kNumRows = inputShape->data[1];
102 const uint32_t kNumCols = inputShape->data[2];
103
104 audio::AdMelSpectrogram melSpec = audio::AdMelSpectrogram(frameLength);
105 melSpec.Init();
106
107 /* Deduce the data length required for 1 inference from the network parameters. */
108 const uint8_t inputResizeScale = 2;
109 const uint32_t audioDataWindowSize = (((inputResizeScale * kNumCols) - 1) * frameStride) + frameLength;
110
111 /* We are choosing to move by 20 frames across the audio for each inference. */
112 const uint8_t nMelSpecVectorsInAudioStride = 20;
113
114 auto audioDataStride = nMelSpecVectorsInAudioStride * frameStride;
115
116 do {
Richard Burton9b8d67a2021-12-10 12:32:51 +0000117 platform.data_psn->clear(COLOR_BLACK);
118
alexander3c798932021-03-26 21:42:19 +0000119 auto currentIndex = ctx.Get<uint32_t>("clipIndex");
120
121 /* Get the output index to look at based on id in the filename. */
122 int8_t machineOutputIndex = OutputIndexFromFileName(get_filename(currentIndex));
123 if (machineOutputIndex == -1) {
124 return false;
125 }
126
127 /* Creating a Mel Spectrogram sliding window for the data required for 1 inference.
128 * "resizing" done here by multiplying stride by resize scale. */
129 auto audioMelSpecWindowSlider = audio::SlidingWindow<const int16_t>(
130 get_audio_array(currentIndex),
131 audioDataWindowSize, frameLength,
132 frameStride * inputResizeScale);
133
134 /* Creating a sliding window through the whole audio clip. */
135 auto audioDataSlider = audio::SlidingWindow<const int16_t>(
136 get_audio_array(currentIndex),
137 get_audio_array_size(currentIndex),
138 audioDataWindowSize, audioDataStride);
139
140 /* Calculate number of the feature vectors in the window overlap region taking into account resizing.
141 * These feature vectors will be reused.*/
142 auto numberOfReusedFeatureVectors = kNumRows - (nMelSpecVectorsInAudioStride / inputResizeScale);
143
144 /* Construct feature calculation function. */
145 auto melSpecFeatureCalc = GetFeatureCalculator(melSpec, inputTensor,
146 numberOfReusedFeatureVectors, trainingMean);
147 if (!melSpecFeatureCalc){
148 return false;
149 }
150
151 /* Result is an averaged sum over inferences. */
152 float result = 0;
153
154 /* Display message on the LCD - inference running. */
155 std::string str_inf{"Running inference... "};
156 platform.data_psn->present_data_text(
157 str_inf.c_str(), str_inf.size(),
158 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
Kshitij Sisodiaf9c19ea2021-05-07 16:08:14 +0100159 info("Running inference on audio clip %" PRIu32 " => %s\n", currentIndex, get_filename(currentIndex));
alexander3c798932021-03-26 21:42:19 +0000160
161 /* Start sliding through audio clip. */
162 while (audioDataSlider.HasNext()) {
163 const int16_t *inferenceWindow = audioDataSlider.Next();
164
165 /* We moved to the next window - set the features sliding to the new address. */
166 audioMelSpecWindowSlider.Reset(inferenceWindow);
167
168 /* The first window does not have cache ready. */
169 bool useCache = audioDataSlider.Index() > 0 && numberOfReusedFeatureVectors > 0;
170
171 /* Start calculating features inside one audio sliding window. */
172 while (audioMelSpecWindowSlider.HasNext()) {
173 const int16_t *melSpecWindow = audioMelSpecWindowSlider.Next();
174 std::vector<int16_t> melSpecAudioData = std::vector<int16_t>(melSpecWindow,
175 melSpecWindow + frameLength);
176
177 /* Compute features for this window and write them to input tensor. */
178 melSpecFeatureCalc(melSpecAudioData, audioMelSpecWindowSlider.Index(),
179 useCache, nMelSpecVectorsInAudioStride, inputResizeScale);
180 }
181
182 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1,
183 audioDataSlider.TotalStrides() + 1);
184
185 /* Run inference over this audio clip sliding window */
alexander27b62d92021-05-04 20:46:08 +0100186 if (!RunInference(model, profiler)) {
187 return false;
188 }
alexander3c798932021-03-26 21:42:19 +0000189
190 /* Use the negative softmax score of the corresponding index as the outlier score */
191 std::vector<float> dequantOutput = Dequantize<int8_t>(outputTensor);
192 Softmax(dequantOutput);
193 result += -dequantOutput[machineOutputIndex];
194
195#if VERIFY_TEST_OUTPUT
196 arm::app::DumpTensor(outputTensor);
197#endif /* VERIFY_TEST_OUTPUT */
198 } /* while (audioDataSlider.HasNext()) */
199
200 /* Use average over whole clip as final score. */
201 result /= (audioDataSlider.TotalStrides() + 1);
202
203 /* Erase. */
204 str_inf = std::string(str_inf.size(), ' ');
205 platform.data_psn->present_data_text(
206 str_inf.c_str(), str_inf.size(),
207 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
208
209 ctx.Set<float>("result", result);
alexanderc350cdc2021-04-29 20:36:09 +0100210 if (!PresentInferenceResult(platform, result, scoreThreshold)) {
alexander3c798932021-03-26 21:42:19 +0000211 return false;
212 }
213
Isabella Gottardi8df12f32021-04-07 17:15:31 +0100214 profiler.PrintProfilingResult();
215
Éanna Ó Catháin8f958872021-09-15 09:32:30 +0100216 IncrementAppCtxIfmIdx(ctx,"clipIndex");
alexander3c798932021-03-26 21:42:19 +0000217
218 } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
219
220 return true;
221 }
222
alexander3c798932021-03-26 21:42:19 +0000223
alexanderc350cdc2021-04-29 20:36:09 +0100224 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold)
alexander3c798932021-03-26 21:42:19 +0000225 {
226 constexpr uint32_t dataPsnTxtStartX1 = 20;
227 constexpr uint32_t dataPsnTxtStartY1 = 30;
228 constexpr uint32_t dataPsnTxtYIncr = 16; /* Row index increment */
229
230 platform.data_psn->set_text_color(COLOR_GREEN);
231
232 /* Display each result */
233 uint32_t rowIdx1 = dataPsnTxtStartY1 + 2 * dataPsnTxtYIncr;
234
George Gekov93e59512021-08-03 11:18:41 +0100235 std::string anomalyScore = std::string{"Average anomaly score is: "} + std::to_string(result);
236 std::string anomalyThreshold = std::string("Anomaly threshold is: ") + std::to_string(threshold);
alexander3c798932021-03-26 21:42:19 +0000237
George Gekov93e59512021-08-03 11:18:41 +0100238 std::string anomalyResult;
alexander3c798932021-03-26 21:42:19 +0000239 if (result > threshold) {
George Gekov93e59512021-08-03 11:18:41 +0100240 anomalyResult += std::string("Anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000241 } else {
George Gekov93e59512021-08-03 11:18:41 +0100242 anomalyResult += std::string("Everything fine, no anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000243 }
244
245 platform.data_psn->present_data_text(
George Gekov93e59512021-08-03 11:18:41 +0100246 anomalyScore.c_str(), anomalyScore.size(),
alexanderc350cdc2021-04-29 20:36:09 +0100247 dataPsnTxtStartX1, rowIdx1, false);
alexander3c798932021-03-26 21:42:19 +0000248
George Gekov93e59512021-08-03 11:18:41 +0100249 info("%s\n", anomalyScore.c_str());
250 info("%s\n", anomalyThreshold.c_str());
251 info("%s\n", anomalyResult.c_str());
alexander3c798932021-03-26 21:42:19 +0000252
253 return true;
254 }
255
256 /**
257 * @brief Generic feature calculator factory.
258 *
259 * Returns lambda function to compute features using features cache.
260 * Real features math is done by a lambda function provided as a parameter.
261 * Features are written to input tensor memory.
262 *
263 * @tparam T feature vector type.
264 * @param inputTensor model input tensor pointer.
265 * @param cacheSize number of feature vectors to cache. Defined by the sliding window overlap.
266 * @param compute features calculator function.
267 * @return lambda function to compute features.
268 */
269 template<class T>
270 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100271 FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
272 std::function<std::vector<T> (std::vector<int16_t>& )> compute)
alexander3c798932021-03-26 21:42:19 +0000273 {
274 /* Feature cache to be captured by lambda function*/
275 static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
276
277 return [=](std::vector<int16_t>& audioDataWindow,
278 size_t index,
279 bool useCache,
280 size_t featuresOverlapIndex,
281 size_t resizeScale)
282 {
283 T *tensorData = tflite::GetTensorData<T>(inputTensor);
284 std::vector<T> features;
285
286 /* Reuse features from cache if cache is ready and sliding windows overlap.
287 * Overlap is in the beginning of sliding window with a size of a feature cache. */
288 if (useCache && index < featureCache.size()) {
289 features = std::move(featureCache[index]);
290 } else {
291 features = std::move(compute(audioDataWindow));
292 }
293 auto size = features.size() / resizeScale;
294 auto sizeBytes = sizeof(T);
295
296 /* Input should be transposed and "resized" by skipping elements. */
297 for (size_t outIndex = 0; outIndex < size; outIndex++) {
298 std::memcpy(tensorData + (outIndex*size) + index, &features[outIndex*resizeScale], sizeBytes);
299 }
300
301 /* Start renewing cache as soon iteration goes out of the windows overlap. */
302 if (index >= featuresOverlapIndex / resizeScale) {
303 featureCache[index - featuresOverlapIndex / resizeScale] = std::move(features);
304 }
305 };
306 }
307
308 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100309 FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
310 size_t cacheSize,
311 std::function<std::vector<int8_t> (std::vector<int16_t>&)> compute);
312
313 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
314 FeatureCalc<uint8_t>(TfLiteTensor* inputTensor,
alexander3c798932021-03-26 21:42:19 +0000315 size_t cacheSize,
alexanderc350cdc2021-04-29 20:36:09 +0100316 std::function<std::vector<uint8_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000317
318 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100319 FeatureCalc<int16_t>(TfLiteTensor* inputTensor,
320 size_t cacheSize,
321 std::function<std::vector<int16_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000322
323 template std::function<void(std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100324 FeatureCalc<float>(TfLiteTensor *inputTensor,
325 size_t cacheSize,
326 std::function<std::vector<float>(std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000327
328
329 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
330 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, TfLiteTensor* inputTensor, size_t cacheSize, float trainingMean)
331 {
332 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)> melSpecFeatureCalc;
333
334 TfLiteQuantization quant = inputTensor->quantization;
335
336 if (kTfLiteAffineQuantization == quant.type) {
337
338 auto *quantParams = (TfLiteAffineQuantization *) quant.params;
339 const float quantScale = quantParams->scale->data[0];
340 const int quantOffset = quantParams->zero_point->data[0];
341
342 switch (inputTensor->type) {
343 case kTfLiteInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100344 melSpecFeatureCalc = FeatureCalc<int8_t>(inputTensor,
345 cacheSize,
346 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
347 return melSpec.MelSpecComputeQuant<int8_t>(
348 audioDataWindow,
349 quantScale,
350 quantOffset,
351 trainingMean);
352 }
alexander3c798932021-03-26 21:42:19 +0000353 );
354 break;
355 }
356 case kTfLiteUInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100357 melSpecFeatureCalc = FeatureCalc<uint8_t>(inputTensor,
358 cacheSize,
359 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
360 return melSpec.MelSpecComputeQuant<uint8_t>(
361 audioDataWindow,
362 quantScale,
363 quantOffset,
364 trainingMean);
365 }
alexander3c798932021-03-26 21:42:19 +0000366 );
367 break;
368 }
369 case kTfLiteInt16: {
alexanderc350cdc2021-04-29 20:36:09 +0100370 melSpecFeatureCalc = FeatureCalc<int16_t>(inputTensor,
371 cacheSize,
372 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
373 return melSpec.MelSpecComputeQuant<int16_t>(
374 audioDataWindow,
375 quantScale,
376 quantOffset,
377 trainingMean);
378 }
alexander3c798932021-03-26 21:42:19 +0000379 );
380 break;
381 }
382 default:
383 printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
384 }
385
386
387 } else {
alexanderc350cdc2021-04-29 20:36:09 +0100388 melSpecFeatureCalc = melSpecFeatureCalc = FeatureCalc<float>(inputTensor,
389 cacheSize,
390 [=, &melSpec](
391 std::vector<int16_t>& audioDataWindow) {
392 return melSpec.ComputeMelSpec(
393 audioDataWindow,
394 trainingMean);
395 });
alexander3c798932021-03-26 21:42:19 +0000396 }
397 return melSpecFeatureCalc;
398 }
399
400} /* namespace app */
401} /* namespace arm */