pcod-common v1.0.0
Shared preprocessing and postprocessing for point-cloud object detection
Loading...
Searching...
No Matches
test_py_cpp_contract.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
5#include "pcod_common/nms.hpp"
7
8#include <cassert>
9#include <cctype>
10#include <cmath>
11#include <cstdio>
12#include <fstream>
13#include <sstream>
14#include <string>
15#include <utility>
16#include <vector>
17
18namespace {
19
21struct CommandResult {
22 int exit_code = -1;
23 std::string stdout_text;
24};
25
30std::string Trim(const std::string& value) {
31 std::size_t first = 0;
32 while (first < value.size() && std::isspace(static_cast<unsigned char>(value[first]))) {
33 ++first;
34 }
35 std::size_t last = value.size();
36 while (last > first && std::isspace(static_cast<unsigned char>(value[last - 1]))) {
37 --last;
38 }
39 return value.substr(first, last - first);
40}
41
46CommandResult RunCommandCapture(const std::string& command) {
47 CommandResult result;
48 FILE* pipe = popen(command.c_str(), "r");
49 if (pipe == nullptr) {
50 return result;
51 }
52 char buffer[256];
53 while (fgets(buffer, static_cast<int>(sizeof(buffer)), pipe) != nullptr) {
54 result.stdout_text += buffer;
55 }
56 result.exit_code = pclose(pipe);
57 return result;
58}
59
64std::vector<std::string> SplitLines(const std::string& text) {
65 std::vector<std::string> lines;
66 std::stringstream ss(text);
67 std::string line;
68 while (std::getline(ss, line)) {
69 lines.push_back(Trim(line));
70 }
71 return lines;
72}
73
78std::vector<float> ParseCsvFloats(const std::string& csv) {
79 std::vector<float> out;
80 std::stringstream ss(csv);
81 std::string item;
82 while (std::getline(ss, item, ',')) {
83 const std::string trimmed = Trim(item);
84 if (!trimmed.empty()) {
85 out.push_back(std::stof(trimmed));
86 }
87 }
88 return out;
89}
90
95void WriteManifestWithScoreThreshold(const std::string& path, const std::string& score_threshold_value) {
96 std::ofstream out(path);
97 out << "schema_version: '2.0'\n";
98 out << "artifact:\n";
99 out << " bundle_name: 'pbod_fp16_gpu_onnx_test'\n";
100 out << " export_format: 'onnx_fp16_gpu'\n";
101 out << " backend: 'onnx'\n";
102 out << " precision: 'fp16'\n";
103 out << " device: 'cuda'\n";
104 out << " files:\n";
105 out << " model: 'model.onnx'\n";
106 out << " checkpoint: 'checkpoints/best.pt'\n";
107 out << " resolved_training_config: 'config/resolved_training_config.yml'\n";
108 out << " triton:\n";
109 out << " enabled: false\n";
110 out << " inputs:\n";
111 out << " - name: 'point_features'\n";
112 out << " dtype: 'float16'\n";
113 out << " shape: ['batch', 100, 1]\n";
114 out << " outputs:\n";
115 out << " - name: 'reg_logits'\n";
116 out << " dtype: 'float16'\n";
117 out << " shape: ['batch', 300, 21]\n";
118 out << "frozen_contract:\n";
119 out << " preprocessing:\n";
120 out << " max_num_points: 100\n";
121 out << " num_point_features: 1\n";
122 out << " point_cloud_range:\n";
123 out << " x: [-1.0, 1.0]\n";
124 out << " y: [-1.0, 1.0]\n";
125 out << " z: [-1.0, 1.0]\n";
126 out << " voxel_size:\n";
127 out << " x: 0.1\n";
128 out << " y: 0.1\n";
129 out << " z: 0.1\n";
130 out << " point_feature_normalization:\n";
131 out << " type: value_threshold\n";
132 out << " epsilon: 1e-6\n";
133 out << " postprocessing:\n";
134 out << " grid_size:\n";
135 out << " x: 10\n";
136 out << " y: 10\n";
137 out << " num_classes: 3\n";
138 out << " class_names: ['car', 'pedestrian', 'truck']\n";
139 out << " model:\n";
140 out << " stride: [2, 1, 2]\n";
141 out << " up_stride: [1, 1, 2]\n";
142 out << " first_up_stride: 1\n";
143 out << " pillar_map_size: [10, 10]\n";
144 out << " pillar_map_range: [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]]\n";
145 out << "runtime_defaults:\n";
146 out << " preprocessing:\n";
147 out << " point_feature:\n";
148 out << " value_threshold: 1.0\n";
149 out << " postprocessing:\n";
150 out << " class_score_threshold: 0.0\n";
151 out << " nms:\n";
152 out << " score_threshold: " << score_threshold_value << "\n";
153 out << " iou_threshold: 0.5\n";
154 out << " max_num_objects: 10\n";
155}
156
157} // namespace
158
160int main() {
161 const std::string python_dir = PCOD_COMMON_PYTHON_DIR;
162 const std::string py_prefix = "PYTHONPATH='" + python_dir + "' python3 -c \"";
163
164 {
165 const auto result = RunCommandCapture(py_prefix + "from pcod_common.manifest import SCHEMA_VERSION; print(SCHEMA_VERSION)\"");
166 assert(result.exit_code == 0);
167 assert(Trim(result.stdout_text) == pcod_common::kManifestSchemaVersion);
168 }
169
170 {
171 const auto result = RunCommandCapture(py_prefix +
172 "from pcod_common.manifest import score_threshold_list as s; "
173 "print(','.join(str(v) for v in s(None))); "
174 "print(','.join(str(v) for v in s(0.25))); "
175 "print(','.join(str(v) for v in s([0.1, 0.2])))\"");
176 assert(result.exit_code == 0);
177 const auto lines = SplitLines(result.stdout_text);
178 assert(lines.size() == 3);
179 assert(lines[0].empty());
180 assert(lines[1] == "0.25");
181 assert(lines[2] == "0.1,0.2");
182 }
183
184 {
185 const std::string scalar_path = "./test_py_cpp_score_scalar.yml";
186 const std::string list_path = "./test_py_cpp_score_list.yml";
187 WriteManifestWithScoreThreshold(scalar_path, "0.2");
188 WriteManifestWithScoreThreshold(list_path, "[0.1, 0.2, 0.3]");
189
190 auto scalar_manifest = pcod_common::LoadModelManifest(scalar_path);
191 auto list_manifest = pcod_common::LoadModelManifest(list_path);
192
193 assert(scalar_manifest.runtime_defaults.postprocessing.nms_score_thresholds.size() == 1);
194 assert(std::abs(scalar_manifest.runtime_defaults.postprocessing.nms_score_thresholds[0] - 0.2f) < 1e-6f);
195 assert(list_manifest.runtime_defaults.postprocessing.nms_score_thresholds.size() == 3);
196 assert(std::abs(list_manifest.runtime_defaults.postprocessing.nms_score_thresholds[0] - 0.1f) < 1e-6f);
197 assert(std::abs(list_manifest.runtime_defaults.postprocessing.nms_score_thresholds[1] - 0.2f) < 1e-6f);
198 assert(std::abs(list_manifest.runtime_defaults.postprocessing.nms_score_thresholds[2] - 0.3f) < 1e-6f);
199 }
200
201 {
202 const auto torch_check = RunCommandCapture(py_prefix + "import torch, torchvision; print('ok')\"");
203 if (torch_check.exit_code == 0) {
205 a.center = {0.0f, 0.0f};
206 a.length = 1.0f;
207 a.width = 1.0f;
208 a.existence_probability = 0.9f;
209 a.classification.push_back({0, 1.0f});
210
212 b.center = {0.1f, 0.0f};
213 b.existence_probability = 0.8f;
214
216 c.center = {10.0f, 0.0f};
217 c.existence_probability = 0.7f;
218
219 std::vector<pcod_common::BoundingBox> boxes = {a, b, c};
221 cfg.score_thresholds = {0.5f};
222 cfg.iou_threshold = 0.1f;
223 cfg.max_detections = 10;
224 cfg.internal_score_threshold = 0.5f;
226
227 std::vector<float> cpp_kept_x;
228 for (const auto& box : boxes) {
229 cpp_kept_x.push_back(box.center[0]);
230 }
231
232 const auto py_nms = RunCommandCapture(py_prefix +
233 "from pcod_common.postprocess import apply_nms; "
234 "import torch; "
235 "boxes=torch.tensor([[0.0,0.0,0.0,1.0,1.0,1.0,0.0],[0.1,0.0,0.0,1.0,1.0,1.0,0.0],[10."
236 "0,0.0,0.0,1.0,1.0,1.0,0.0]],dtype=torch.float32); "
237 "scores=torch.tensor([0.9,0.8,0.7],dtype=torch.float32); "
238 "labels=torch.tensor([0,0,0],dtype=torch.long); "
239 "kept_boxes,_,_=apply_nms(boxes,scores,labels,[0.5],0.1,10,use_rotated=False); "
240 "print(','.join(str(float(v)) for v in kept_boxes[:,0].tolist()))\"");
241 assert(py_nms.exit_code == 0);
242
243 const auto py_kept_x = ParseCsvFloats(py_nms.stdout_text);
244 assert(py_kept_x.size() == cpp_kept_x.size());
245 for (std::size_t i = 0; i < py_kept_x.size(); ++i) {
246 assert(std::abs(py_kept_x[i] - cpp_kept_x[i]) < 1e-5f);
247 }
248 }
249 }
250
251 return 0;
252}
constexpr const char * kManifestSchemaVersion
Definition version.hpp:10
void ApplyRotatedNms(std::vector< BoundingBox > &bboxes, const NmsConfig &config)
Definition nms.cpp:12
ModelManifest LoadModelManifest(const std::string &path)
float length
Length along the local X axis.
std::array< float, 2 > center
XY center in metres.
float width
Width along the local Y axis.
std::vector< ClassificationEntry > classification
Ranked semantic predictions.
float existence_probability
Detection confidence.
float iou_threshold
IoU above which a lower-scored box is suppressed.
Definition nms.hpp:15
std::vector< float > score_thresholds
Per-class thresholds, or one threshold shared by all classes.
Definition nms.hpp:14
int max_detections
Maximum number of boxes retained.
Definition nms.hpp:16
float internal_score_threshold
Score pivot used when rescaling class thresholds.
Definition nms.hpp:17