blob: 420e6d4a380683922cb52e0f769d465e4a00795d [file] [log] [blame]
alexander3c798932021-03-26 21:42:19 +00001/*
2 * Copyright (c) 2021 Arm Limited. All rights reserved.
3 * 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"
25#include "UseCaseCommonUtils.hpp"
26#include "AdPostProcessing.hpp"
alexander31ae9f02022-02-10 16:15:54 +000027#include "log_macros.h"
alexander3c798932021-03-26 21:42:19 +000028
29namespace arm {
30namespace app {
31
32 /**
alexander3c798932021-03-26 21:42:19 +000033 * @brief Presents inference results using the data presentation
34 * object.
35 * @param[in] platform reference to the hal platform object
36 * @param[in] result average sum of classification results
Isabella Gottardi56ee6202021-05-12 08:27:15 +010037 * @param[in] threshold if larger than this value we have an anomaly
alexander3c798932021-03-26 21:42:19 +000038 * @return true if successful, false otherwise
39 **/
alexanderc350cdc2021-04-29 20:36:09 +010040 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold);
alexander3c798932021-03-26 21:42:19 +000041
42 /**
43 * @brief Returns a function to perform feature calculation and populates input tensor data with
44 * MelSpe data.
45 *
46 * Input tensor data type check is performed to choose correct MFCC feature data type.
47 * If tensor has an integer data type then original features are quantised.
48 *
49 * Warning: mfcc calculator provided as input must have the same life scope as returned function.
50 *
Isabella Gottardi56ee6202021-05-12 08:27:15 +010051 * @param[in] melSpec MFCC feature calculator.
52 * @param[in,out] inputTensor Input tensor pointer to store calculated features.
53 * @param[in] cacheSize Size of the feture vectors cache (number of feature vectors).
54 * @param[in] trainingMean Training mean.
alexander3c798932021-03-26 21:42:19 +000055 * @return function function to be called providing audio sample and sliding window index.
56 */
57 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
58 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec,
59 TfLiteTensor* inputTensor,
60 size_t cacheSize,
61 float trainingMean);
62
63 /* Vibration classification handler */
64 bool ClassifyVibrationHandler(ApplicationContext& ctx, uint32_t clipIndex, bool runAll)
65 {
66 auto& platform = ctx.Get<hal_platform&>("platform");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010067 auto& profiler = ctx.Get<Profiler&>("profiler");
alexander3c798932021-03-26 21:42:19 +000068
69 constexpr uint32_t dataPsnTxtInfStartX = 20;
70 constexpr uint32_t dataPsnTxtInfStartY = 40;
71
alexander3c798932021-03-26 21:42:19 +000072 auto& model = ctx.Get<Model&>("model");
73
74 /* If the request has a valid size, set the audio index */
75 if (clipIndex < NUMBER_OF_FILES) {
Éanna Ó Catháin8f958872021-09-15 09:32:30 +010076 if (!SetAppCtxIfmIdx(ctx, clipIndex,"clipIndex")) {
alexander3c798932021-03-26 21:42:19 +000077 return false;
78 }
79 }
80 if (!model.IsInited()) {
81 printf_err("Model is not initialised! Terminating processing.\n");
82 return false;
83 }
84
85 const auto frameLength = ctx.Get<int>("frameLength");
86 const auto frameStride = ctx.Get<int>("frameStride");
87 const auto scoreThreshold = ctx.Get<float>("scoreThreshold");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010088 const auto trainingMean = ctx.Get<float>("trainingMean");
alexander3c798932021-03-26 21:42:19 +000089 auto startClipIdx = ctx.Get<uint32_t>("clipIndex");
90
91 TfLiteTensor* outputTensor = model.GetOutputTensor(0);
92 TfLiteTensor* inputTensor = model.GetInputTensor(0);
93
94 if (!inputTensor->dims) {
95 printf_err("Invalid input tensor dims\n");
96 return false;
97 }
98
99 TfLiteIntArray* inputShape = model.GetInputShape(0);
100 const uint32_t kNumRows = inputShape->data[1];
101 const uint32_t kNumCols = inputShape->data[2];
102
103 audio::AdMelSpectrogram melSpec = audio::AdMelSpectrogram(frameLength);
104 melSpec.Init();
105
106 /* Deduce the data length required for 1 inference from the network parameters. */
107 const uint8_t inputResizeScale = 2;
108 const uint32_t audioDataWindowSize = (((inputResizeScale * kNumCols) - 1) * frameStride) + frameLength;
109
110 /* We are choosing to move by 20 frames across the audio for each inference. */
111 const uint8_t nMelSpecVectorsInAudioStride = 20;
112
113 auto audioDataStride = nMelSpecVectorsInAudioStride * frameStride;
114
115 do {
Richard Burton9b8d67a2021-12-10 12:32:51 +0000116 platform.data_psn->clear(COLOR_BLACK);
117
alexander3c798932021-03-26 21:42:19 +0000118 auto currentIndex = ctx.Get<uint32_t>("clipIndex");
119
120 /* Get the output index to look at based on id in the filename. */
121 int8_t machineOutputIndex = OutputIndexFromFileName(get_filename(currentIndex));
122 if (machineOutputIndex == -1) {
123 return false;
124 }
125
126 /* Creating a Mel Spectrogram sliding window for the data required for 1 inference.
127 * "resizing" done here by multiplying stride by resize scale. */
128 auto audioMelSpecWindowSlider = audio::SlidingWindow<const int16_t>(
129 get_audio_array(currentIndex),
130 audioDataWindowSize, frameLength,
131 frameStride * inputResizeScale);
132
133 /* Creating a sliding window through the whole audio clip. */
134 auto audioDataSlider = audio::SlidingWindow<const int16_t>(
135 get_audio_array(currentIndex),
136 get_audio_array_size(currentIndex),
137 audioDataWindowSize, audioDataStride);
138
139 /* Calculate number of the feature vectors in the window overlap region taking into account resizing.
140 * These feature vectors will be reused.*/
141 auto numberOfReusedFeatureVectors = kNumRows - (nMelSpecVectorsInAudioStride / inputResizeScale);
142
143 /* Construct feature calculation function. */
144 auto melSpecFeatureCalc = GetFeatureCalculator(melSpec, inputTensor,
145 numberOfReusedFeatureVectors, trainingMean);
146 if (!melSpecFeatureCalc){
147 return false;
148 }
149
150 /* Result is an averaged sum over inferences. */
151 float result = 0;
152
153 /* Display message on the LCD - inference running. */
154 std::string str_inf{"Running inference... "};
155 platform.data_psn->present_data_text(
156 str_inf.c_str(), str_inf.size(),
157 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
Kshitij Sisodiaf9c19ea2021-05-07 16:08:14 +0100158 info("Running inference on audio clip %" PRIu32 " => %s\n", currentIndex, get_filename(currentIndex));
alexander3c798932021-03-26 21:42:19 +0000159
160 /* Start sliding through audio clip. */
161 while (audioDataSlider.HasNext()) {
162 const int16_t *inferenceWindow = audioDataSlider.Next();
163
164 /* We moved to the next window - set the features sliding to the new address. */
165 audioMelSpecWindowSlider.Reset(inferenceWindow);
166
167 /* The first window does not have cache ready. */
168 bool useCache = audioDataSlider.Index() > 0 && numberOfReusedFeatureVectors > 0;
169
170 /* Start calculating features inside one audio sliding window. */
171 while (audioMelSpecWindowSlider.HasNext()) {
172 const int16_t *melSpecWindow = audioMelSpecWindowSlider.Next();
173 std::vector<int16_t> melSpecAudioData = std::vector<int16_t>(melSpecWindow,
174 melSpecWindow + frameLength);
175
176 /* Compute features for this window and write them to input tensor. */
177 melSpecFeatureCalc(melSpecAudioData, audioMelSpecWindowSlider.Index(),
178 useCache, nMelSpecVectorsInAudioStride, inputResizeScale);
179 }
180
181 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1,
182 audioDataSlider.TotalStrides() + 1);
183
184 /* Run inference over this audio clip sliding window */
alexander27b62d92021-05-04 20:46:08 +0100185 if (!RunInference(model, profiler)) {
186 return false;
187 }
alexander3c798932021-03-26 21:42:19 +0000188
189 /* Use the negative softmax score of the corresponding index as the outlier score */
190 std::vector<float> dequantOutput = Dequantize<int8_t>(outputTensor);
191 Softmax(dequantOutput);
192 result += -dequantOutput[machineOutputIndex];
193
194#if VERIFY_TEST_OUTPUT
195 arm::app::DumpTensor(outputTensor);
196#endif /* VERIFY_TEST_OUTPUT */
197 } /* while (audioDataSlider.HasNext()) */
198
199 /* Use average over whole clip as final score. */
200 result /= (audioDataSlider.TotalStrides() + 1);
201
202 /* Erase. */
203 str_inf = std::string(str_inf.size(), ' ');
204 platform.data_psn->present_data_text(
205 str_inf.c_str(), str_inf.size(),
206 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
207
208 ctx.Set<float>("result", result);
alexanderc350cdc2021-04-29 20:36:09 +0100209 if (!PresentInferenceResult(platform, result, scoreThreshold)) {
alexander3c798932021-03-26 21:42:19 +0000210 return false;
211 }
212
Isabella Gottardi8df12f32021-04-07 17:15:31 +0100213 profiler.PrintProfilingResult();
214
Éanna Ó Catháin8f958872021-09-15 09:32:30 +0100215 IncrementAppCtxIfmIdx(ctx,"clipIndex");
alexander3c798932021-03-26 21:42:19 +0000216
217 } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
218
219 return true;
220 }
221
alexander3c798932021-03-26 21:42:19 +0000222
alexanderc350cdc2021-04-29 20:36:09 +0100223 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold)
alexander3c798932021-03-26 21:42:19 +0000224 {
225 constexpr uint32_t dataPsnTxtStartX1 = 20;
226 constexpr uint32_t dataPsnTxtStartY1 = 30;
227 constexpr uint32_t dataPsnTxtYIncr = 16; /* Row index increment */
228
229 platform.data_psn->set_text_color(COLOR_GREEN);
230
231 /* Display each result */
232 uint32_t rowIdx1 = dataPsnTxtStartY1 + 2 * dataPsnTxtYIncr;
233
George Gekov93e59512021-08-03 11:18:41 +0100234 std::string anomalyScore = std::string{"Average anomaly score is: "} + std::to_string(result);
235 std::string anomalyThreshold = std::string("Anomaly threshold is: ") + std::to_string(threshold);
alexander3c798932021-03-26 21:42:19 +0000236
George Gekov93e59512021-08-03 11:18:41 +0100237 std::string anomalyResult;
alexander3c798932021-03-26 21:42:19 +0000238 if (result > threshold) {
George Gekov93e59512021-08-03 11:18:41 +0100239 anomalyResult += std::string("Anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000240 } else {
George Gekov93e59512021-08-03 11:18:41 +0100241 anomalyResult += std::string("Everything fine, no anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000242 }
243
244 platform.data_psn->present_data_text(
George Gekov93e59512021-08-03 11:18:41 +0100245 anomalyScore.c_str(), anomalyScore.size(),
alexanderc350cdc2021-04-29 20:36:09 +0100246 dataPsnTxtStartX1, rowIdx1, false);
alexander3c798932021-03-26 21:42:19 +0000247
George Gekov93e59512021-08-03 11:18:41 +0100248 info("%s\n", anomalyScore.c_str());
249 info("%s\n", anomalyThreshold.c_str());
250 info("%s\n", anomalyResult.c_str());
alexander3c798932021-03-26 21:42:19 +0000251
252 return true;
253 }
254
255 /**
256 * @brief Generic feature calculator factory.
257 *
258 * Returns lambda function to compute features using features cache.
259 * Real features math is done by a lambda function provided as a parameter.
260 * Features are written to input tensor memory.
261 *
262 * @tparam T feature vector type.
263 * @param inputTensor model input tensor pointer.
264 * @param cacheSize number of feature vectors to cache. Defined by the sliding window overlap.
265 * @param compute features calculator function.
266 * @return lambda function to compute features.
267 */
268 template<class T>
269 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100270 FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
271 std::function<std::vector<T> (std::vector<int16_t>& )> compute)
alexander3c798932021-03-26 21:42:19 +0000272 {
273 /* Feature cache to be captured by lambda function*/
274 static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
275
276 return [=](std::vector<int16_t>& audioDataWindow,
277 size_t index,
278 bool useCache,
279 size_t featuresOverlapIndex,
280 size_t resizeScale)
281 {
282 T *tensorData = tflite::GetTensorData<T>(inputTensor);
283 std::vector<T> features;
284
285 /* Reuse features from cache if cache is ready and sliding windows overlap.
286 * Overlap is in the beginning of sliding window with a size of a feature cache. */
287 if (useCache && index < featureCache.size()) {
288 features = std::move(featureCache[index]);
289 } else {
290 features = std::move(compute(audioDataWindow));
291 }
292 auto size = features.size() / resizeScale;
293 auto sizeBytes = sizeof(T);
294
295 /* Input should be transposed and "resized" by skipping elements. */
296 for (size_t outIndex = 0; outIndex < size; outIndex++) {
297 std::memcpy(tensorData + (outIndex*size) + index, &features[outIndex*resizeScale], sizeBytes);
298 }
299
300 /* Start renewing cache as soon iteration goes out of the windows overlap. */
301 if (index >= featuresOverlapIndex / resizeScale) {
302 featureCache[index - featuresOverlapIndex / resizeScale] = std::move(features);
303 }
304 };
305 }
306
307 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100308 FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
309 size_t cacheSize,
310 std::function<std::vector<int8_t> (std::vector<int16_t>&)> compute);
311
312 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
313 FeatureCalc<uint8_t>(TfLiteTensor* inputTensor,
alexander3c798932021-03-26 21:42:19 +0000314 size_t cacheSize,
alexanderc350cdc2021-04-29 20:36:09 +0100315 std::function<std::vector<uint8_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000316
317 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100318 FeatureCalc<int16_t>(TfLiteTensor* inputTensor,
319 size_t cacheSize,
320 std::function<std::vector<int16_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000321
322 template std::function<void(std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100323 FeatureCalc<float>(TfLiteTensor *inputTensor,
324 size_t cacheSize,
325 std::function<std::vector<float>(std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000326
327
328 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
329 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, TfLiteTensor* inputTensor, size_t cacheSize, float trainingMean)
330 {
331 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)> melSpecFeatureCalc;
332
333 TfLiteQuantization quant = inputTensor->quantization;
334
335 if (kTfLiteAffineQuantization == quant.type) {
336
337 auto *quantParams = (TfLiteAffineQuantization *) quant.params;
338 const float quantScale = quantParams->scale->data[0];
339 const int quantOffset = quantParams->zero_point->data[0];
340
341 switch (inputTensor->type) {
342 case kTfLiteInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100343 melSpecFeatureCalc = FeatureCalc<int8_t>(inputTensor,
344 cacheSize,
345 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
346 return melSpec.MelSpecComputeQuant<int8_t>(
347 audioDataWindow,
348 quantScale,
349 quantOffset,
350 trainingMean);
351 }
alexander3c798932021-03-26 21:42:19 +0000352 );
353 break;
354 }
355 case kTfLiteUInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100356 melSpecFeatureCalc = FeatureCalc<uint8_t>(inputTensor,
357 cacheSize,
358 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
359 return melSpec.MelSpecComputeQuant<uint8_t>(
360 audioDataWindow,
361 quantScale,
362 quantOffset,
363 trainingMean);
364 }
alexander3c798932021-03-26 21:42:19 +0000365 );
366 break;
367 }
368 case kTfLiteInt16: {
alexanderc350cdc2021-04-29 20:36:09 +0100369 melSpecFeatureCalc = FeatureCalc<int16_t>(inputTensor,
370 cacheSize,
371 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
372 return melSpec.MelSpecComputeQuant<int16_t>(
373 audioDataWindow,
374 quantScale,
375 quantOffset,
376 trainingMean);
377 }
alexander3c798932021-03-26 21:42:19 +0000378 );
379 break;
380 }
381 default:
382 printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
383 }
384
385
386 } else {
alexanderc350cdc2021-04-29 20:36:09 +0100387 melSpecFeatureCalc = melSpecFeatureCalc = FeatureCalc<float>(inputTensor,
388 cacheSize,
389 [=, &melSpec](
390 std::vector<int16_t>& audioDataWindow) {
391 return melSpec.ComputeMelSpec(
392 audioDataWindow,
393 trainingMean);
394 });
alexander3c798932021-03-26 21:42:19 +0000395 }
396 return melSpecFeatureCalc;
397 }
398
399} /* namespace app */
400} /* namespace arm */