IVGCVSW-4454 Remove the CounterSet and Device fields from Category
[platform/upstream/armnn.git] / tests / YoloInferenceTest.hpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #pragma once
6
7 #include "InferenceTest.hpp"
8 #include "YoloDatabase.hpp"
9
10 #include <algorithm>
11 #include <array>
12 #include <utility>
13
14 #include <boost/assert.hpp>
15 #include <boost/multi_array.hpp>
16 #include <boost/test/tools/floating_point_comparison.hpp>
17
18 constexpr size_t YoloOutputSize = 1470;
19
20 template <typename Model>
21 class YoloTestCase : public InferenceModelTestCase<Model>
22 {
23 public:
24     YoloTestCase(Model& model,
25         unsigned int testCaseId,
26         YoloTestCaseData& testCaseData)
27      : InferenceModelTestCase<Model>(model, testCaseId, { std::move(testCaseData.m_InputImage) }, { YoloOutputSize })
28      , m_FloatComparer(boost::math::fpc::percent_tolerance(1.0f))
29      , m_TopObjectDetections(std::move(testCaseData.m_TopObjectDetections))
30     {
31     }
32
33     virtual TestCaseResult ProcessResult(const InferenceTestOptions& options) override
34     {
35         boost::ignore_unused(options);
36
37         using Boost3dArray = boost::multi_array<float, 3>;
38
39         const std::vector<float>& output = boost::get<std::vector<float>>(this->GetOutputs()[0]);
40         BOOST_ASSERT(output.size() == YoloOutputSize);
41
42         constexpr Boost3dArray::index gridSize = 7;
43         constexpr Boost3dArray::index numClasses = 20;
44         constexpr Boost3dArray::index numScales = 2;
45
46         const float* outputPtr =  output.data();
47
48         // Range 0-980. Class probabilities. 7x7x20
49         Boost3dArray classProbabilities(boost::extents[gridSize][gridSize][numClasses]);
50         for (Boost3dArray::index y = 0; y < gridSize; ++y)
51         {
52             for (Boost3dArray::index x = 0; x < gridSize; ++x)
53             {
54                 for (Boost3dArray::index c = 0; c < numClasses; ++c)
55                 {
56                     classProbabilities[y][x][c] = *outputPtr++;
57                 }
58             }
59         }
60
61         // Range 980-1078. Scales. 7x7x2
62         Boost3dArray scales(boost::extents[gridSize][gridSize][numScales]);
63         for (Boost3dArray::index y = 0; y < gridSize; ++y)
64         {
65             for (Boost3dArray::index x = 0; x < gridSize; ++x)
66             {
67                 for (Boost3dArray::index s = 0; s < numScales; ++s)
68                 {
69                     scales[y][x][s] = *outputPtr++;
70                 }
71             }
72         }
73
74         // Range 1078-1469. Bounding boxes. 7x7x2x4
75         constexpr float imageWidthAsFloat = static_cast<float>(YoloImageWidth);
76         constexpr float imageHeightAsFloat = static_cast<float>(YoloImageHeight);
77
78         boost::multi_array<float, 4> boxes(boost::extents[gridSize][gridSize][numScales][4]);
79         for (Boost3dArray::index y = 0; y < gridSize; ++y)
80         {
81             for (Boost3dArray::index x = 0; x < gridSize; ++x)
82             {
83                 for (Boost3dArray::index s = 0; s < numScales; ++s)
84                 {
85                     float bx = *outputPtr++;
86                     float by = *outputPtr++;
87                     float bw = *outputPtr++;
88                     float bh = *outputPtr++;
89
90                     boxes[y][x][s][0] = ((bx + static_cast<float>(x)) / 7.0f) * imageWidthAsFloat;
91                     boxes[y][x][s][1] = ((by + static_cast<float>(y)) / 7.0f) * imageHeightAsFloat;
92                     boxes[y][x][s][2] = bw * bw * static_cast<float>(imageWidthAsFloat);
93                     boxes[y][x][s][3] = bh * bh * static_cast<float>(imageHeightAsFloat);
94                 }
95             }
96         }
97         BOOST_ASSERT(output.data() + YoloOutputSize == outputPtr);
98
99         std::vector<YoloDetectedObject> detectedObjects;
100         detectedObjects.reserve(gridSize * gridSize * numScales * numClasses);
101
102         for (Boost3dArray::index y = 0; y < gridSize; ++y)
103         {
104             for (Boost3dArray::index x = 0; x < gridSize; ++x)
105             {
106                 for (Boost3dArray::index s = 0; s < numScales; ++s)
107                 {
108                     for (Boost3dArray::index c = 0; c < numClasses; ++c)
109                     {
110                         // Resolved confidence: class probabilities * scales.
111                         const float confidence = classProbabilities[y][x][c] * scales[y][x][s];
112
113                         // Resolves bounding box and stores.
114                         YoloBoundingBox box;
115                         box.m_X = boxes[y][x][s][0];
116                         box.m_Y = boxes[y][x][s][1];
117                         box.m_W = boxes[y][x][s][2];
118                         box.m_H = boxes[y][x][s][3];
119
120                         detectedObjects.emplace_back(c, box, confidence);
121                     }
122                 }
123             }
124         }
125
126         // Sorts detected objects by confidence.
127         std::sort(detectedObjects.begin(), detectedObjects.end(),
128             [](const YoloDetectedObject& a, const YoloDetectedObject& b)
129             {
130                 // Sorts by largest confidence first, then by class.
131                 return a.m_Confidence > b.m_Confidence
132                     || (a.m_Confidence == b.m_Confidence && a.m_Class > b.m_Class);
133             });
134
135         // Checks the top N detections.
136         auto outputIt  = detectedObjects.begin();
137         auto outputEnd = detectedObjects.end();
138
139         for (const YoloDetectedObject& expectedDetection : m_TopObjectDetections)
140         {
141             if (outputIt == outputEnd)
142             {
143                 // Somehow expected more things to check than detections found by the model.
144                 return TestCaseResult::Abort;
145             }
146
147             const YoloDetectedObject& detectedObject = *outputIt;
148             if (detectedObject.m_Class != expectedDetection.m_Class)
149             {
150                 ARMNN_LOG(error) << "Prediction for test case " << this->GetTestCaseId() <<
151                     " is incorrect: Expected (" << expectedDetection.m_Class << ")" <<
152                     " but predicted (" << detectedObject.m_Class << ")";
153                 return TestCaseResult::Failed;
154             }
155
156             if (!m_FloatComparer(detectedObject.m_Box.m_X, expectedDetection.m_Box.m_X) ||
157                 !m_FloatComparer(detectedObject.m_Box.m_Y, expectedDetection.m_Box.m_Y) ||
158                 !m_FloatComparer(detectedObject.m_Box.m_W, expectedDetection.m_Box.m_W) ||
159                 !m_FloatComparer(detectedObject.m_Box.m_H, expectedDetection.m_Box.m_H) ||
160                 !m_FloatComparer(detectedObject.m_Confidence, expectedDetection.m_Confidence))
161             {
162                 ARMNN_LOG(error) << "Detected bounding box for test case " << this->GetTestCaseId() <<
163                     " is incorrect";
164                 return TestCaseResult::Failed;
165             }
166
167             ++outputIt;
168         }
169
170         return TestCaseResult::Ok;
171     }
172
173 private:
174     boost::math::fpc::close_at_tolerance<float> m_FloatComparer;
175     std::vector<YoloDetectedObject> m_TopObjectDetections;
176 };
177
178 template <typename Model>
179 class YoloTestCaseProvider : public IInferenceTestCaseProvider
180 {
181 public:
182     template <typename TConstructModelCallable>
183     explicit YoloTestCaseProvider(TConstructModelCallable constructModel)
184         : m_ConstructModel(constructModel)
185     {
186     }
187
188     virtual void AddCommandLineOptions(boost::program_options::options_description& options) override
189     {
190         namespace po = boost::program_options;
191
192         options.add_options()
193             ("data-dir,d", po::value<std::string>(&m_DataDir)->required(),
194                 "Path to directory containing test data");
195
196         Model::AddCommandLineOptions(options, m_ModelCommandLineOptions);
197     }
198
199     virtual bool ProcessCommandLineOptions(const InferenceTestOptions &commonOptions) override
200     {
201         if (!ValidateDirectory(m_DataDir))
202         {
203             return false;
204         }
205
206         m_Model = m_ConstructModel(commonOptions, m_ModelCommandLineOptions);
207         if (!m_Model)
208         {
209             return false;
210         }
211
212         m_Database = std::make_unique<YoloDatabase>(m_DataDir.c_str());
213         if (!m_Database)
214         {
215             return false;
216         }
217
218         return true;
219     }
220
221     virtual std::unique_ptr<IInferenceTestCase> GetTestCase(unsigned int testCaseId) override
222     {
223         std::unique_ptr<YoloTestCaseData> testCaseData = m_Database->GetTestCaseData(testCaseId);
224         if (!testCaseData)
225         {
226             return nullptr;
227         }
228
229         return std::make_unique<YoloTestCase<Model>>(*m_Model, testCaseId, *testCaseData);
230     }
231
232 private:
233     typename Model::CommandLineOptions m_ModelCommandLineOptions;
234     std::function<std::unique_ptr<Model>(const InferenceTestOptions&,
235                                          typename Model::CommandLineOptions)> m_ConstructModel;
236     std::unique_ptr<Model> m_Model;
237
238     std::string m_DataDir;
239     std::unique_ptr<YoloDatabase> m_Database;
240 };