blob: e99821fa1a84be2740d9189013214396e597c6a0 [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 /**
32 * @brief Helper function to increment current audio clip index
33 * @param[in/out] ctx pointer to the application context object
34 **/
alexanderc350cdc2021-04-29 20:36:09 +010035 static void IncrementAppCtxClipIdx(ApplicationContext& ctx);
alexander3c798932021-03-26 21:42:19 +000036
37 /**
38 * @brief Helper function to set the audio clip index
39 * @param[in/out] ctx pointer to the application context object
40 * @param[in] idx value to be set
41 * @return true if index is set, false otherwise
42 **/
alexanderc350cdc2021-04-29 20:36:09 +010043 static bool SetAppCtxClipIdx(ApplicationContext& ctx, uint32_t idx);
alexander3c798932021-03-26 21:42:19 +000044
45 /**
46 * @brief Presents inference results using the data presentation
47 * object.
48 * @param[in] platform reference to the hal platform object
49 * @param[in] result average sum of classification results
50 * @param[in] threhsold if larger than this value we have an anomaly
51 * @return true if successful, false otherwise
52 **/
alexanderc350cdc2021-04-29 20:36:09 +010053 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold);
alexander3c798932021-03-26 21:42:19 +000054
55 /**
56 * @brief Returns a function to perform feature calculation and populates input tensor data with
57 * MelSpe data.
58 *
59 * Input tensor data type check is performed to choose correct MFCC feature data type.
60 * If tensor has an integer data type then original features are quantised.
61 *
62 * Warning: mfcc calculator provided as input must have the same life scope as returned function.
63 *
64 * @param[in] mfcc MFCC feature calculator.
65 * @param[in/out] inputTensor Input tensor pointer to store calculated features.
66 * @param[i] cacheSize Size of the feture vectors cache (number of feature vectors).
67 * @return function function to be called providing audio sample and sliding window index.
68 */
69 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
70 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec,
71 TfLiteTensor* inputTensor,
72 size_t cacheSize,
73 float trainingMean);
74
75 /* Vibration classification handler */
76 bool ClassifyVibrationHandler(ApplicationContext& ctx, uint32_t clipIndex, bool runAll)
77 {
78 auto& platform = ctx.Get<hal_platform&>("platform");
Isabella Gottardi8df12f32021-04-07 17:15:31 +010079 auto& profiler = ctx.Get<Profiler&>("profiler");
alexander3c798932021-03-26 21:42:19 +000080
81 constexpr uint32_t dataPsnTxtInfStartX = 20;
82 constexpr uint32_t dataPsnTxtInfStartY = 40;
83
84 platform.data_psn->clear(COLOR_BLACK);
85
86 auto& model = ctx.Get<Model&>("model");
87
88 /* If the request has a valid size, set the audio index */
89 if (clipIndex < NUMBER_OF_FILES) {
alexanderc350cdc2021-04-29 20:36:09 +010090 if (!SetAppCtxClipIdx(ctx, clipIndex)) {
alexander3c798932021-03-26 21:42:19 +000091 return false;
92 }
93 }
94 if (!model.IsInited()) {
95 printf_err("Model is not initialised! Terminating processing.\n");
96 return false;
97 }
98
99 const auto frameLength = ctx.Get<int>("frameLength");
100 const auto frameStride = ctx.Get<int>("frameStride");
101 const auto scoreThreshold = ctx.Get<float>("scoreThreshold");
Isabella Gottardi8df12f32021-04-07 17:15:31 +0100102 const auto trainingMean = ctx.Get<float>("trainingMean");
alexander3c798932021-03-26 21:42:19 +0000103 auto startClipIdx = ctx.Get<uint32_t>("clipIndex");
104
105 TfLiteTensor* outputTensor = model.GetOutputTensor(0);
106 TfLiteTensor* inputTensor = model.GetInputTensor(0);
107
108 if (!inputTensor->dims) {
109 printf_err("Invalid input tensor dims\n");
110 return false;
111 }
112
113 TfLiteIntArray* inputShape = model.GetInputShape(0);
114 const uint32_t kNumRows = inputShape->data[1];
115 const uint32_t kNumCols = inputShape->data[2];
116
117 audio::AdMelSpectrogram melSpec = audio::AdMelSpectrogram(frameLength);
118 melSpec.Init();
119
120 /* Deduce the data length required for 1 inference from the network parameters. */
121 const uint8_t inputResizeScale = 2;
122 const uint32_t audioDataWindowSize = (((inputResizeScale * kNumCols) - 1) * frameStride) + frameLength;
123
124 /* We are choosing to move by 20 frames across the audio for each inference. */
125 const uint8_t nMelSpecVectorsInAudioStride = 20;
126
127 auto audioDataStride = nMelSpecVectorsInAudioStride * frameStride;
128
129 do {
130 auto currentIndex = ctx.Get<uint32_t>("clipIndex");
131
132 /* Get the output index to look at based on id in the filename. */
133 int8_t machineOutputIndex = OutputIndexFromFileName(get_filename(currentIndex));
134 if (machineOutputIndex == -1) {
135 return false;
136 }
137
138 /* Creating a Mel Spectrogram sliding window for the data required for 1 inference.
139 * "resizing" done here by multiplying stride by resize scale. */
140 auto audioMelSpecWindowSlider = audio::SlidingWindow<const int16_t>(
141 get_audio_array(currentIndex),
142 audioDataWindowSize, frameLength,
143 frameStride * inputResizeScale);
144
145 /* Creating a sliding window through the whole audio clip. */
146 auto audioDataSlider = audio::SlidingWindow<const int16_t>(
147 get_audio_array(currentIndex),
148 get_audio_array_size(currentIndex),
149 audioDataWindowSize, audioDataStride);
150
151 /* Calculate number of the feature vectors in the window overlap region taking into account resizing.
152 * These feature vectors will be reused.*/
153 auto numberOfReusedFeatureVectors = kNumRows - (nMelSpecVectorsInAudioStride / inputResizeScale);
154
155 /* Construct feature calculation function. */
156 auto melSpecFeatureCalc = GetFeatureCalculator(melSpec, inputTensor,
157 numberOfReusedFeatureVectors, trainingMean);
158 if (!melSpecFeatureCalc){
159 return false;
160 }
161
162 /* Result is an averaged sum over inferences. */
163 float result = 0;
164
165 /* Display message on the LCD - inference running. */
166 std::string str_inf{"Running inference... "};
167 platform.data_psn->present_data_text(
168 str_inf.c_str(), str_inf.size(),
169 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
170 info("Running inference on audio clip %u => %s\n", currentIndex, get_filename(currentIndex));
171
172 /* Start sliding through audio clip. */
173 while (audioDataSlider.HasNext()) {
174 const int16_t *inferenceWindow = audioDataSlider.Next();
175
176 /* We moved to the next window - set the features sliding to the new address. */
177 audioMelSpecWindowSlider.Reset(inferenceWindow);
178
179 /* The first window does not have cache ready. */
180 bool useCache = audioDataSlider.Index() > 0 && numberOfReusedFeatureVectors > 0;
181
182 /* Start calculating features inside one audio sliding window. */
183 while (audioMelSpecWindowSlider.HasNext()) {
184 const int16_t *melSpecWindow = audioMelSpecWindowSlider.Next();
185 std::vector<int16_t> melSpecAudioData = std::vector<int16_t>(melSpecWindow,
186 melSpecWindow + frameLength);
187
188 /* Compute features for this window and write them to input tensor. */
189 melSpecFeatureCalc(melSpecAudioData, audioMelSpecWindowSlider.Index(),
190 useCache, nMelSpecVectorsInAudioStride, inputResizeScale);
191 }
192
193 info("Inference %zu/%zu\n", audioDataSlider.Index() + 1,
194 audioDataSlider.TotalStrides() + 1);
195
196 /* Run inference over this audio clip sliding window */
alexander27b62d92021-05-04 20:46:08 +0100197 if (!RunInference(model, profiler)) {
198 return false;
199 }
alexander3c798932021-03-26 21:42:19 +0000200
201 /* Use the negative softmax score of the corresponding index as the outlier score */
202 std::vector<float> dequantOutput = Dequantize<int8_t>(outputTensor);
203 Softmax(dequantOutput);
204 result += -dequantOutput[machineOutputIndex];
205
206#if VERIFY_TEST_OUTPUT
207 arm::app::DumpTensor(outputTensor);
208#endif /* VERIFY_TEST_OUTPUT */
209 } /* while (audioDataSlider.HasNext()) */
210
211 /* Use average over whole clip as final score. */
212 result /= (audioDataSlider.TotalStrides() + 1);
213
214 /* Erase. */
215 str_inf = std::string(str_inf.size(), ' ');
216 platform.data_psn->present_data_text(
217 str_inf.c_str(), str_inf.size(),
218 dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0);
219
220 ctx.Set<float>("result", result);
alexanderc350cdc2021-04-29 20:36:09 +0100221 if (!PresentInferenceResult(platform, result, scoreThreshold)) {
alexander3c798932021-03-26 21:42:19 +0000222 return false;
223 }
224
Isabella Gottardi8df12f32021-04-07 17:15:31 +0100225 profiler.PrintProfilingResult();
226
alexanderc350cdc2021-04-29 20:36:09 +0100227 IncrementAppCtxClipIdx(ctx);
alexander3c798932021-03-26 21:42:19 +0000228
229 } while (runAll && ctx.Get<uint32_t>("clipIndex") != startClipIdx);
230
231 return true;
232 }
233
alexanderc350cdc2021-04-29 20:36:09 +0100234 static void IncrementAppCtxClipIdx(ApplicationContext& ctx)
alexander3c798932021-03-26 21:42:19 +0000235 {
236 auto curAudioIdx = ctx.Get<uint32_t>("clipIndex");
237
238 if (curAudioIdx + 1 >= NUMBER_OF_FILES) {
239 ctx.Set<uint32_t>("clipIndex", 0);
240 return;
241 }
242 ++curAudioIdx;
243 ctx.Set<uint32_t>("clipIndex", curAudioIdx);
244 }
245
alexanderc350cdc2021-04-29 20:36:09 +0100246 static bool SetAppCtxClipIdx(ApplicationContext& ctx, uint32_t idx)
alexander3c798932021-03-26 21:42:19 +0000247 {
248 if (idx >= NUMBER_OF_FILES) {
249 printf_err("Invalid idx %u (expected less than %u)\n",
250 idx, NUMBER_OF_FILES);
251 return false;
252 }
253 ctx.Set<uint32_t>("clipIndex", idx);
254 return true;
255 }
256
alexanderc350cdc2021-04-29 20:36:09 +0100257 static bool PresentInferenceResult(hal_platform& platform, float result, float threshold)
alexander3c798932021-03-26 21:42:19 +0000258 {
259 constexpr uint32_t dataPsnTxtStartX1 = 20;
260 constexpr uint32_t dataPsnTxtStartY1 = 30;
261 constexpr uint32_t dataPsnTxtYIncr = 16; /* Row index increment */
262
263 platform.data_psn->set_text_color(COLOR_GREEN);
264
265 /* Display each result */
266 uint32_t rowIdx1 = dataPsnTxtStartY1 + 2 * dataPsnTxtYIncr;
267
268 std::string resultStr = std::string{"Average anomaly score is: "} + std::to_string(result) +
269 std::string("\n") + std::string("Anomaly threshold is: ") + std::to_string(threshold) +
270 std::string("\n");
271
272 if (result > threshold) {
273 resultStr += std::string("Anomaly detected!");
274 } else {
275 resultStr += std::string("Everything fine, no anomaly detected!");
276 }
277
278 platform.data_psn->present_data_text(
279 resultStr.c_str(), resultStr.size(),
alexanderc350cdc2021-04-29 20:36:09 +0100280 dataPsnTxtStartX1, rowIdx1, false);
alexander3c798932021-03-26 21:42:19 +0000281
282 info("%s\n", resultStr.c_str());
283
284 return true;
285 }
286
287 /**
288 * @brief Generic feature calculator factory.
289 *
290 * Returns lambda function to compute features using features cache.
291 * Real features math is done by a lambda function provided as a parameter.
292 * Features are written to input tensor memory.
293 *
294 * @tparam T feature vector type.
295 * @param inputTensor model input tensor pointer.
296 * @param cacheSize number of feature vectors to cache. Defined by the sliding window overlap.
297 * @param compute features calculator function.
298 * @return lambda function to compute features.
299 */
300 template<class T>
301 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100302 FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize,
303 std::function<std::vector<T> (std::vector<int16_t>& )> compute)
alexander3c798932021-03-26 21:42:19 +0000304 {
305 /* Feature cache to be captured by lambda function*/
306 static std::vector<std::vector<T>> featureCache = std::vector<std::vector<T>>(cacheSize);
307
308 return [=](std::vector<int16_t>& audioDataWindow,
309 size_t index,
310 bool useCache,
311 size_t featuresOverlapIndex,
312 size_t resizeScale)
313 {
314 T *tensorData = tflite::GetTensorData<T>(inputTensor);
315 std::vector<T> features;
316
317 /* Reuse features from cache if cache is ready and sliding windows overlap.
318 * Overlap is in the beginning of sliding window with a size of a feature cache. */
319 if (useCache && index < featureCache.size()) {
320 features = std::move(featureCache[index]);
321 } else {
322 features = std::move(compute(audioDataWindow));
323 }
324 auto size = features.size() / resizeScale;
325 auto sizeBytes = sizeof(T);
326
327 /* Input should be transposed and "resized" by skipping elements. */
328 for (size_t outIndex = 0; outIndex < size; outIndex++) {
329 std::memcpy(tensorData + (outIndex*size) + index, &features[outIndex*resizeScale], sizeBytes);
330 }
331
332 /* Start renewing cache as soon iteration goes out of the windows overlap. */
333 if (index >= featuresOverlapIndex / resizeScale) {
334 featureCache[index - featuresOverlapIndex / resizeScale] = std::move(features);
335 }
336 };
337 }
338
339 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100340 FeatureCalc<int8_t>(TfLiteTensor* inputTensor,
341 size_t cacheSize,
342 std::function<std::vector<int8_t> (std::vector<int16_t>&)> compute);
343
344 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
345 FeatureCalc<uint8_t>(TfLiteTensor* inputTensor,
alexander3c798932021-03-26 21:42:19 +0000346 size_t cacheSize,
alexanderc350cdc2021-04-29 20:36:09 +0100347 std::function<std::vector<uint8_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000348
349 template std::function<void (std::vector<int16_t>&, size_t , bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100350 FeatureCalc<int16_t>(TfLiteTensor* inputTensor,
351 size_t cacheSize,
352 std::function<std::vector<int16_t> (std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000353
354 template std::function<void(std::vector<int16_t>&, size_t, bool, size_t, size_t)>
alexanderc350cdc2021-04-29 20:36:09 +0100355 FeatureCalc<float>(TfLiteTensor *inputTensor,
356 size_t cacheSize,
357 std::function<std::vector<float>(std::vector<int16_t>&)> compute);
alexander3c798932021-03-26 21:42:19 +0000358
359
360 static std::function<void (std::vector<int16_t>&, int, bool, size_t, size_t)>
361 GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, TfLiteTensor* inputTensor, size_t cacheSize, float trainingMean)
362 {
363 std::function<void (std::vector<int16_t>&, size_t, bool, size_t, size_t)> melSpecFeatureCalc;
364
365 TfLiteQuantization quant = inputTensor->quantization;
366
367 if (kTfLiteAffineQuantization == quant.type) {
368
369 auto *quantParams = (TfLiteAffineQuantization *) quant.params;
370 const float quantScale = quantParams->scale->data[0];
371 const int quantOffset = quantParams->zero_point->data[0];
372
373 switch (inputTensor->type) {
374 case kTfLiteInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100375 melSpecFeatureCalc = FeatureCalc<int8_t>(inputTensor,
376 cacheSize,
377 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
378 return melSpec.MelSpecComputeQuant<int8_t>(
379 audioDataWindow,
380 quantScale,
381 quantOffset,
382 trainingMean);
383 }
alexander3c798932021-03-26 21:42:19 +0000384 );
385 break;
386 }
387 case kTfLiteUInt8: {
alexanderc350cdc2021-04-29 20:36:09 +0100388 melSpecFeatureCalc = FeatureCalc<uint8_t>(inputTensor,
389 cacheSize,
390 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
391 return melSpec.MelSpecComputeQuant<uint8_t>(
392 audioDataWindow,
393 quantScale,
394 quantOffset,
395 trainingMean);
396 }
alexander3c798932021-03-26 21:42:19 +0000397 );
398 break;
399 }
400 case kTfLiteInt16: {
alexanderc350cdc2021-04-29 20:36:09 +0100401 melSpecFeatureCalc = FeatureCalc<int16_t>(inputTensor,
402 cacheSize,
403 [=, &melSpec](std::vector<int16_t>& audioDataWindow) {
404 return melSpec.MelSpecComputeQuant<int16_t>(
405 audioDataWindow,
406 quantScale,
407 quantOffset,
408 trainingMean);
409 }
alexander3c798932021-03-26 21:42:19 +0000410 );
411 break;
412 }
413 default:
414 printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type));
415 }
416
417
418 } else {
alexanderc350cdc2021-04-29 20:36:09 +0100419 melSpecFeatureCalc = melSpecFeatureCalc = FeatureCalc<float>(inputTensor,
420 cacheSize,
421 [=, &melSpec](
422 std::vector<int16_t>& audioDataWindow) {
423 return melSpec.ComputeMelSpec(
424 audioDataWindow,
425 trainingMean);
426 });
alexander3c798932021-03-26 21:42:19 +0000427 }
428 return melSpecFeatureCalc;
429 }
430
431} /* namespace app */
432} /* namespace arm */