blob: b20b63e9fc2a0c705fee400848133096006b8508 [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"
27
28namespace arm {
29namespace app {
30
31 /**
alexander3c798932021-03-26 21:42:19 +000032 * @brief Presents inference results using the data presentation
33 * object.
34 * @param[in] platform reference to the hal platform object
35 * @param[in] result average sum of classification results
Isabella Gottardi56ee6202021-05-12 08:27:15 +010036 * @param[in] threshold if larger than this value we have an anomaly
alexander3c798932021-03-26 21:42:19 +000037 * @return true if successful, false otherwise
38 **/
alexanderc350cdc2021-04-29 20:36:09 +010039 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold);
alexander3c798932021-03-26 21:42:19 +000040
41 /**
42 * @brief Returns a function to perform feature calculation and populates input tensor data with
43 * MelSpe data.
44 *
45 * Input tensor data type check is performed to choose correct MFCC feature data type.
46 * If tensor has an integer data type then original features are quantised.
47 *
48 * Warning: mfcc calculator provided as input must have the same life scope as returned function.
49 *
Isabella Gottardi56ee6202021-05-12 08:27:15 +010050 * @param[in] melSpec MFCC feature calculator.
51 * @param[in,out] inputTensor Input tensor pointer to store calculated features.
52 * @param[in] cacheSize Size of the feture vectors cache (number of feature vectors).
53 * @param[in] trainingMean Training mean.
alexander3c798932021-03-26 21:42:19 +000054 * @return function function to be called providing audio sample and sliding window index.
55 */
56 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
57 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec,
58 TfLiteTensor* inputTensor,
59 size_t cacheSize,
60 float trainingMean);
61
62 /* Vibration classification handler */
63 bool ClassifyVibrationHandler(ApplicationContext& ctx, uint32_t clipIndex, bool runAll)
64 {
65 auto& platform = ctx.Get<hal_platform&>("platform");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010066 auto& profiler = ctx.Get<Profiler&>("profiler");
alexander3c798932021-03-26 21:42:19 +000067
68 constexpr uint32_t dataPsnTxtInfStartX = 20;
69 constexpr uint32_t dataPsnTxtInfStartY = 40;
70
71 platform.data_psn->clear(COLOR_BLACK);
72
73 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 {
117 auto currentIndex = ctx.Get<uint32_t>("clipIndex");
118
119 /* Get the output index to look at based on id in the filename. */
120 int8_t machineOutputIndex = OutputIndexFromFileName(get_filename(currentIndex));
121 if (machineOutputIndex == -1) {
122 return false;
123 }
124
125 /* Creating a Mel Spectrogram sliding window for the data required for 1 inference.
126 * "resizing" done here by multiplying stride by resize scale. */
127 auto audioMelSpecWindowSlider = audio::SlidingWindow<const int16_t>(
128 get_audio_array(currentIndex),
129 audioDataWindowSize, frameLength,
130 frameStride * inputResizeScale);
131
132 /* Creating a sliding window through the whole audio clip. */
133 auto audioDataSlider = audio::SlidingWindow<const int16_t>(
134 get_audio_array(currentIndex),
135 get_audio_array_size(currentIndex),
136 audioDataWindowSize, audioDataStride);
137
138 /* Calculate number of the feature vectors in the window overlap region taking into account resizing.
139 * These feature vectors will be reused.*/
140 auto numberOfReusedFeatureVectors = kNumRows - (nMelSpecVectorsInAudioStride / inputResizeScale);
141
142 /* Construct feature calculation function. */
143 auto melSpecFeatureCalc = GetFeatureCalculator(melSpec, inputTensor,
144 numberOfReusedFeatureVectors, trainingMean);
145 if (!melSpecFeatureCalc){
146 return false;
147 }
148
149 /* Result is an averaged sum over inferences. */
150 float result = 0;
151
152 /* Display message on the LCD - inference running. */
153 std::string str_inf{"Running inference... "};
154 platform.data_psn->present_data_text(
155 str_inf.c_str(), str_inf.size(),
156 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
Kshitij Sisodiaf9c19ea2021-05-07 16:08:14 +0100157 info("Running inference on audio clip %" PRIu32 " => %s\n", currentIndex, get_filename(currentIndex));
alexander3c798932021-03-26 21:42:19 +0000158
159 /* Start sliding through audio clip. */
160 while (audioDataSlider.HasNext()) {
161 const int16_t *inferenceWindow = audioDataSlider.Next();
162
163 /* We moved to the next window - set the features sliding to the new address. */
164 audioMelSpecWindowSlider.Reset(inferenceWindow);
165
166 /* The first window does not have cache ready. */
167 bool useCache = audioDataSlider.Index() > 0 && numberOfReusedFeatureVectors > 0;
168
169 /* Start calculating features inside one audio sliding window. */
170 while (audioMelSpecWindowSlider.HasNext()) {
171 const int16_t *melSpecWindow = audioMelSpecWindowSlider.Next();
172 std::vector<int16_t> melSpecAudioData = std::vector<int16_t>(melSpecWindow,
173 melSpecWindow + frameLength);
174
175 /* Compute features for this window and write them to input tensor. */
176 melSpecFeatureCalc(melSpecAudioData, audioMelSpecWindowSlider.Index(),
177 useCache, nMelSpecVectorsInAudioStride, inputResizeScale);
178 }
179
180 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1,
181 audioDataSlider.TotalStrides() + 1);
182
183 /* Run inference over this audio clip sliding window */
alexander27b62d92021-05-04 20:46:08 +0100184 if (!RunInference(model, profiler)) {
185 return false;
186 }
alexander3c798932021-03-26 21:42:19 +0000187
188 /* Use the negative softmax score of the corresponding index as the outlier score */
189 std::vector<float> dequantOutput = Dequantize<int8_t>(outputTensor);
190 Softmax(dequantOutput);
191 result += -dequantOutput[machineOutputIndex];
192
193#if VERIFY_TEST_OUTPUT
194 arm::app::DumpTensor(outputTensor);
195#endif /* VERIFY_TEST_OUTPUT */
196 } /* while (audioDataSlider.HasNext()) */
197
198 /* Use average over whole clip as final score. */
199 result /= (audioDataSlider.TotalStrides() + 1);
200
201 /* Erase. */
202 str_inf = std::string(str_inf.size(), ' ');
203 platform.data_psn->present_data_text(
204 str_inf.c_str(), str_inf.size(),
205 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
206
207 ctx.Set<float>("result", result);
alexanderc350cdc2021-04-29 20:36:09 +0100208 if (!PresentInferenceResult(platform, result, scoreThreshold)) {
alexander3c798932021-03-26 21:42:19 +0000209 return false;
210 }
211
Isabella Gottardi8df12f32021-04-07 17:15:31 +0100212 profiler.PrintProfilingResult();
213
Éanna Ó Catháin8f958872021-09-15 09:32:30 +0100214 IncrementAppCtxIfmIdx(ctx,"clipIndex");
alexander3c798932021-03-26 21:42:19 +0000215
216 } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
217
218 return true;
219 }
220
alexander3c798932021-03-26 21:42:19 +0000221
alexanderc350cdc2021-04-29 20:36:09 +0100222 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold)
alexander3c798932021-03-26 21:42:19 +0000223 {
224 constexpr uint32_t dataPsnTxtStartX1 = 20;
225 constexpr uint32_t dataPsnTxtStartY1 = 30;
226 constexpr uint32_t dataPsnTxtYIncr = 16; /* Row index increment */
227
228 platform.data_psn->set_text_color(COLOR_GREEN);
229
230 /* Display each result */
231 uint32_t rowIdx1 = dataPsnTxtStartY1 + 2 * dataPsnTxtYIncr;
232
George Gekov93e59512021-08-03 11:18:41 +0100233 std::string anomalyScore = std::string{"Average anomaly score is: "} + std::to_string(result);
234 std::string anomalyThreshold = std::string("Anomaly threshold is: ") + std::to_string(threshold);
alexander3c798932021-03-26 21:42:19 +0000235
George Gekov93e59512021-08-03 11:18:41 +0100236 std::string anomalyResult;
alexander3c798932021-03-26 21:42:19 +0000237 if (result > threshold) {
George Gekov93e59512021-08-03 11:18:41 +0100238 anomalyResult += std::string("Anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000239 } else {
George Gekov93e59512021-08-03 11:18:41 +0100240 anomalyResult += std::string("Everything fine, no anomaly detected!");
alexander3c798932021-03-26 21:42:19 +0000241 }
242
243 platform.data_psn->present_data_text(
George Gekov93e59512021-08-03 11:18:41 +0100244 anomalyScore.c_str(), anomalyScore.size(),
alexanderc350cdc2021-04-29 20:36:09 +0100245 dataPsnTxtStartX1, rowIdx1, false);
alexander3c798932021-03-26 21:42:19 +0000246
George Gekov93e59512021-08-03 11:18:41 +0100247 info("%s\n", anomalyScore.c_str());
248 info("%s\n", anomalyThreshold.c_str());
249 info("%s\n", anomalyResult.c_str());
alexander3c798932021-03-26 21:42:19 +0000250
251 return true;
252 }
253
254 /**
255 * @brief Generic feature calculator factory.
256 *
257 * Returns lambda function to compute features using features cache.
258 * Real features math is done by a lambda function provided as a parameter.
259 * Features are written to input tensor memory.
260 *
261 * @tparam T feature vector type.
262 * @param inputTensor model input tensor pointer.
263 * @param cacheSize number of feature vectors to cache. Defined by the sliding window overlap.
264 * @param compute features calculator function.
265 * @return lambda function to compute features.
266 */
267 template<class T>
268 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100269 FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
270 std::function<std::vector<T> (std::vector<int16_t>& )> compute)
alexander3c798932021-03-26 21:42:19 +0000271 {
272 /* Feature cache to be captured by lambda function*/
273 static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
274
275 return [=](std::vector<int16_t>& audioDataWindow,
276 size_t index,
277 bool useCache,
278 size_t featuresOverlapIndex,
279 size_t resizeScale)
280 {
281 T *tensorData = tflite::GetTensorData<T>(inputTensor);
282 std::vector<T> features;
283
284 /* Reuse features from cache if cache is ready and sliding windows overlap.
285 * Overlap is in the beginning of sliding window with a size of a feature cache. */
286 if (useCache && index < featureCache.size()) {
287 features = std::move(featureCache[index]);
288 } else {
289 features = std::move(compute(audioDataWindow));
290 }
291 auto size = features.size() / resizeScale;
292 auto sizeBytes = sizeof(T);
293
294 /* Input should be transposed and "resized" by skipping elements. */
295 for (size_t outIndex = 0; outIndex < size; outIndex++) {
296 std::memcpy(tensorData + (outIndex*size) + index, &features[outIndex*resizeScale], sizeBytes);
297 }
298
299 /* Start renewing cache as soon iteration goes out of the windows overlap. */
300 if (index >= featuresOverlapIndex / resizeScale) {
301 featureCache[index - featuresOverlapIndex / resizeScale] = std::move(features);
302 }
303 };
304 }
305
306 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100307 FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
308 size_t cacheSize,
309 std::function<std::vector<int8_t> (std::vector<int16_t>&)> compute);
310
311 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
312 FeatureCalc<uint8_t>(TfLiteTensor* inputTensor,
alexander3c798932021-03-26 21:42:19 +0000313 size_t cacheSize,
alexanderc350cdc2021-04-29 20:36:09 +0100314 std::function<std::vector<uint8_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000315
316 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100317 FeatureCalc<int16_t>(TfLiteTensor* inputTensor,
318 size_t cacheSize,
319 std::function<std::vector<int16_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000320
321 template std::function<void(std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100322 FeatureCalc<float>(TfLiteTensor *inputTensor,
323 size_t cacheSize,
324 std::function<std::vector<float>(std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000325
326
327 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
328 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, TfLiteTensor* inputTensor, size_t cacheSize, float trainingMean)
329 {
330 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)> melSpecFeatureCalc;
331
332 TfLiteQuantization quant = inputTensor->quantization;
333
334 if (kTfLiteAffineQuantization == quant.type) {
335
336 auto *quantParams = (TfLiteAffineQuantization *) quant.params;
337 const float quantScale = quantParams->scale->data[0];
338 const int quantOffset = quantParams->zero_point->data[0];
339
340 switch (inputTensor->type) {
341 case kTfLiteInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100342 melSpecFeatureCalc = FeatureCalc<int8_t>(inputTensor,
343 cacheSize,
344 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
345 return melSpec.MelSpecComputeQuant<int8_t>(
346 audioDataWindow,
347 quantScale,
348 quantOffset,
349 trainingMean);
350 }
alexander3c798932021-03-26 21:42:19 +0000351 );
352 break;
353 }
354 case kTfLiteUInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100355 melSpecFeatureCalc = FeatureCalc<uint8_t>(inputTensor,
356 cacheSize,
357 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
358 return melSpec.MelSpecComputeQuant<uint8_t>(
359 audioDataWindow,
360 quantScale,
361 quantOffset,
362 trainingMean);
363 }
alexander3c798932021-03-26 21:42:19 +0000364 );
365 break;
366 }
367 case kTfLiteInt16: {
alexanderc350cdc2021-04-29 20:36:09 +0100368 melSpecFeatureCalc = FeatureCalc<int16_t>(inputTensor,
369 cacheSize,
370 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
371 return melSpec.MelSpecComputeQuant<int16_t>(
372 audioDataWindow,
373 quantScale,
374 quantOffset,
375 trainingMean);
376 }
alexander3c798932021-03-26 21:42:19 +0000377 );
378 break;
379 }
380 default:
381 printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
382 }
383
384
385 } else {
alexanderc350cdc2021-04-29 20:36:09 +0100386 melSpecFeatureCalc = melSpecFeatureCalc = FeatureCalc<float>(inputTensor,
387 cacheSize,
388 [=, &melSpec](
389 std::vector<int16_t>& audioDataWindow) {
390 return melSpec.ComputeMelSpec(
391 audioDataWindow,
392 trainingMean);
393 });
alexander3c798932021-03-26 21:42:19 +0000394 }
395 return melSpecFeatureCalc;
396 }
397
398} /* namespace app */
399} /* namespace arm */