pcod-common v1.0.0
Shared preprocessing and postprocessing for point-cloud object detection
Loading...
Searching...
No Matches
pcod_common Namespace Reference

Classes

struct  ArtifactConfig
 
struct  BoundingBox
 
struct  BoundingBoxVertex
 
struct  ClassificationEntry
 
struct  FrozenContract
 
struct  FrozenModelConfig
 
struct  FrozenPostprocessConfig
 
struct  FrozenPreprocessConfig
 
struct  Line
 
struct  ModelManifest
 
struct  NmsConfig
 
struct  PbodOutputsView
 
struct  PbodPostprocessConfig
 
struct  PillarGrid
 
struct  PillarPreprocessCudaConfig
 
class  PillarPreprocessCudaContext
 
struct  PillarPreprocessCudaDeviceOutputs
 
struct  PillarPreprocessCudaOutputs
 
struct  PillarPreprocessPoint
 
struct  PointPreprocessConfig
 
class  PointPreprocessor
 
struct  RuntimeDefaults
 

Enumerations

enum class  PointFeatureNormalizationType { kNone , kValueThreshold , kMinMax , kZScore }
 

Functions

template<typename T >
constexpr const T & clamp (const T &value, const T &low, const T &high)
 
template<typename T >
scale_score (T score, T old_thresh, T new_thresh)
 
float wrap_to_range (float val, float min_val, float max_val)
 
ModelManifest LoadModelManifest (const std::string &path)
 
void ValidateModelManifest (const ModelManifest &manifest)
 
void ApplyRotatedNms (std::vector< BoundingBox > &bboxes, const NmsConfig &config)
 
std::vector< BoundingBoxDecodePbod (const PbodOutputsView &outputs, const PillarGrid &grid, const PbodPostprocessConfig &config)
 
PillarGrid BuildPillarGrid (const std::array< int, 2 > &pillar_map_size, const std::array< std::array< float, 2 >, 3 > &pillar_map_range, int first_up_stride, int stride)
 
bool HasCudaPillarPreprocessSupport ()
 
PointFeatureNormalizationType ParsePointFeatureNormalizationType (const std::string &value)
 

Variables

constexpr const char * kVersion = "1.0.0"
 
constexpr const char * kManifestSchemaVersion = "2.0"
 

Enumeration Type Documentation

◆ PointFeatureNormalizationType

Available normalization strategies for the additional point feature.

Enumerator
kNone 
kValueThreshold 
kMinMax 
kZScore 

Definition at line 12 of file point_preprocess.hpp.

Function Documentation

◆ ApplyRotatedNms()

void pcod_common::ApplyRotatedNms ( std::vector< BoundingBox > & bboxes,
const NmsConfig & config )

Filter and reorder boxes in place.

Parameters
bboxesBoxes to process.
configSuppression configuration.

Definition at line 12 of file nms.cpp.

12 {
13 if (bboxes.empty()) {
14 return;
15 }
16
17 std::vector<std::pair<float, BoundingBox*>> scored;
18 scored.reserve(bboxes.size());
19
20 for (auto& bbox : bboxes) {
21 float max_class_score = 0.0F;
22 std::size_t max_class_idx = 0;
23 for (const auto& entry : bbox.classification) {
24 if (entry.score > max_class_score) {
25 max_class_score = entry.score;
26 max_class_idx = entry.class_idx;
27 }
28 }
29 float class_thresh = config.internal_score_threshold;
30 if (max_class_idx < config.score_thresholds.size()) {
31 class_thresh = config.score_thresholds[max_class_idx];
32 }
33 float score = scale_score(bbox.existence_probability, class_thresh, config.internal_score_threshold);
34 scored.emplace_back(score, &bbox);
35 }
36
37 std::sort(scored.begin(), scored.end(), [](const auto& a, const auto& b) { return a.first > b.first; });
38
39 std::vector<BoundingBox> kept;
40 kept.reserve(bboxes.size());
41
42 for (const auto& candidate : scored) {
43 if (candidate.first < config.internal_score_threshold) {
44 break;
45 }
46 bool keep = true;
47 for (const auto& kept_box : kept) {
48 if (candidate.second->overlaps(kept_box, config.iou_threshold)) {
49 keep = false;
50 break;
51 }
52 }
53 if (keep) {
54 kept.push_back(*candidate.second);
55 if (static_cast<int>(kept.size()) >= config.max_detections) {
56 break;
57 }
58 }
59 }
60
61 bboxes = std::move(kept);
62}
T scale_score(T score, T old_thresh, T new_thresh)
Definition math.hpp:23
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

References pcod_common::NmsConfig::internal_score_threshold, pcod_common::NmsConfig::iou_threshold, pcod_common::NmsConfig::max_detections, scale_score(), and pcod_common::NmsConfig::score_thresholds.

Referenced by main().

◆ BuildPillarGrid()

PillarGrid pcod_common::BuildPillarGrid ( const std::array< int, 2 > & pillar_map_size,
const std::array< std::array< float, 2 >, 3 > & pillar_map_range,
int first_up_stride,
int stride )

Build the output pillar grid represented by an exported model.

Parameters
pillar_map_sizeBase map dimensions in X and Y.
pillar_map_rangeXYZ coordinate ranges.
first_up_strideUpsampling factor applied before model strides.
strideModel output stride.
Returns
Grid dimensions, ranges, and XYZ center coordinates.

Definition at line 10 of file pillar_grid.cpp.

13 {
14 PillarGrid grid;
15 const int size_x = pillar_map_size[0];
16 const int size_y = pillar_map_size[1];
17 const int stride_val = stride > 0 ? stride : 1;
18 const int up_stride = first_up_stride > 0 ? first_up_stride : 1;
19
20 grid.grid_x = size_x * up_stride / stride_val;
21 grid.grid_y = size_y * up_stride / stride_val;
22 grid.x_min = pillar_map_range[0][0];
23 grid.x_max = pillar_map_range[0][1];
24 grid.y_min = pillar_map_range[1][0];
25 grid.y_max = pillar_map_range[1][1];
26 grid.z_min = pillar_map_range[2][0];
27 grid.z_max = pillar_map_range[2][1];
28
29 const float dx = (grid.x_max - grid.x_min) / static_cast<float>(grid.grid_x);
30 const float dy = (grid.y_max - grid.y_min) / static_cast<float>(grid.grid_y);
31 const float half_dx = dx * 0.5F;
32 const float half_dy = dy * 0.5F;
33
34 const int num_pillars = grid.grid_x * grid.grid_y;
35 grid.centers.resize(static_cast<std::size_t>(num_pillars) * 3);
36
37 std::size_t offset = 0;
38 for (int ix = 0; ix < grid.grid_x; ++ix) {
39 const float x_center = grid.x_min + half_dx + static_cast<float>(ix) * dx;
40 for (int iy = 0; iy < grid.grid_y; ++iy) {
41 const float y_center = grid.y_min + half_dy + static_cast<float>(iy) * dy;
42 grid.centers[offset++] = x_center;
43 grid.centers[offset++] = y_center;
44 grid.centers[offset++] = 0.0F;
45 }
46 }
47
48 return grid;
49}
float z_min
Minimum Z coordinate.
float z_max
Maximum Z coordinate.
int grid_x
Number of grid cells along X.
float x_min
Minimum X coordinate.
float y_min
Minimum Y coordinate.
int grid_y
Number of grid cells along Y.
std::vector< float > centers
Flat center array with shape [num_pillars, 3].
float x_max
Maximum X coordinate.
float y_max
Maximum Y coordinate.

References pcod_common::PillarGrid::centers, pcod_common::PillarGrid::grid_x, pcod_common::PillarGrid::grid_y, pcod_common::PillarGrid::x_max, pcod_common::PillarGrid::x_min, pcod_common::PillarGrid::y_max, pcod_common::PillarGrid::y_min, pcod_common::PillarGrid::z_max, and pcod_common::PillarGrid::z_min.

Referenced by main().

◆ clamp()

template<typename T >
const T & pcod_common::clamp ( const T & value,
const T & low,
const T & high )
inlineconstexpr
Parameters
valueInput value.
lowLower bound.
highUpper bound.
Returns
Constrained value.

Definition at line 12 of file math.hpp.

12 {
13 return (value < low) ? low : (high < value) ? high : value;
14}

◆ DecodePbod()

std::vector< BoundingBox > pcod_common::DecodePbod ( const PbodOutputsView & outputs,
const PillarGrid & grid,
const PbodPostprocessConfig & config )

Decode PBOD tensors into oriented boxes.

Parameters
outputsNon-owning model-output tensors.
gridSpatial pillar centers.
configClass names and thresholds.
Returns
Decoded oriented boxes.

Definition at line 24 of file pbod_postprocess.cpp.

24 {
25 std::vector<BoundingBox> objects;
26 if (outputs.focal_logits == nullptr || outputs.size_posterior == nullptr || outputs.class_logits == nullptr ||
27 outputs.reg_logits == nullptr) {
28 throw std::invalid_argument("DecodePbod requires non-null output tensor pointers.");
29 }
30 if (outputs.num_pillars <= 0 || outputs.num_classes <= 0) {
31 return objects;
32 }
33 if (outputs.reg_dim > 0 && outputs.reg_dim < 7) {
34 throw std::invalid_argument("DecodePbod requires reg_dim >= 7.");
35 }
36 if (grid.centers.size() < static_cast<std::size_t>(outputs.num_pillars) * 3) {
37 throw std::invalid_argument("DecodePbod received fewer pillar centers than num_pillars.");
38 }
39
40 const int reg_dim = outputs.reg_dim > 0 ? outputs.reg_dim : 7;
41 const std::size_t num_pillars = static_cast<std::size_t>(outputs.num_pillars);
42 const std::size_t num_classes = static_cast<std::size_t>(outputs.num_classes);
43 const std::size_t reg_dim_size = static_cast<std::size_t>(reg_dim);
44
45 const std::vector<float> focal_logits = CopyTensorView(outputs.focal_logits, num_pillars);
46 const std::vector<float> size_posterior = CopyTensorView(outputs.size_posterior, num_pillars * num_classes * 3U);
47 const std::vector<float> class_logits = CopyTensorView(outputs.class_logits, num_pillars * num_classes);
48 const std::vector<float> reg_logits = CopyTensorView(outputs.reg_logits, num_pillars * num_classes * reg_dim_size);
49
50 objects.reserve(static_cast<std::size_t>(outputs.num_pillars));
51 for (int idx = 0; idx < outputs.num_pillars; ++idx) {
52 const std::size_t pillar_idx = static_cast<std::size_t>(idx);
53 const float score = sigmoid(focal_logits[pillar_idx]);
54
55 int best_class = 0;
56 const std::size_t class_base = pillar_idx * num_classes;
57 float best_logit = class_logits[class_base];
58 for (int c = 1; c < outputs.num_classes; ++c) {
59 float logit = class_logits[class_base + static_cast<std::size_t>(c)];
60 if (logit > best_logit) {
61 best_logit = logit;
62 best_class = c;
63 }
64 }
65
66 float score_thresh = 0.0F;
67 if (!config.score_thresholds.empty()) {
68 const std::size_t class_idx = static_cast<std::size_t>(best_class);
69 score_thresh =
70 class_idx < config.score_thresholds.size() ? config.score_thresholds[class_idx] : config.score_thresholds.front();
71 }
72
73 if (score < score_thresh) {
74 continue;
75 }
76
77 BoundingBox box;
78 const std::size_t class_idx = static_cast<std::size_t>(best_class);
79 const std::size_t size_offset = (pillar_idx * num_classes + class_idx) * 3U;
80 const std::size_t reg_offset = (pillar_idx * num_classes + class_idx) * reg_dim_size;
81 const std::size_t center_offset = pillar_idx * 3U;
82
83 box.length = std::exp(reg_logits[reg_offset + 3U]) * size_posterior[size_offset + 0U];
84 box.width = std::exp(reg_logits[reg_offset + 4U]) * size_posterior[size_offset + 1U];
85 box.height = std::exp(reg_logits[reg_offset + 5U]) * size_posterior[size_offset + 2U];
86 box.center[0] = reg_logits[reg_offset + 0U] * size_posterior[size_offset + 0U] + grid.centers[center_offset + 0U];
87 box.center[1] = reg_logits[reg_offset + 1U] * size_posterior[size_offset + 1U] + grid.centers[center_offset + 1U];
88 box.z = reg_logits[reg_offset + 2U] * size_posterior[size_offset + 2U] + grid.centers[center_offset + 2U];
89 box.yaw = wrap_to_range(reg_logits[reg_offset + 6U], -static_cast<float>(M_PI), static_cast<float>(M_PI));
90 if (std::isnan(box.length) || std::isnan(box.width) || std::isnan(box.height) || std::isnan(box.yaw)) {
91 continue;
92 }
93
94 for (int c = 0; c < outputs.num_classes; ++c) {
95 const std::size_t current_class = static_cast<std::size_t>(c);
96 box.classification.push_back({current_class, class_logits[class_base + current_class]});
97 }
98 box.existence_probability = score;
99
100 objects.push_back(std::move(box));
101 }
102
103 return objects;
104}
float wrap_to_range(float val, float min_val, float max_val)
Definition math.hpp:32
const float * focal_logits
Shape [num_pillars].
int num_classes
Number of semantic classes.
int reg_dim
Regression values per pillar and class.
int num_pillars
Number of spatial pillars.
const float * class_logits
Shape [num_pillars, num_classes].
const float * size_posterior
Shape [num_pillars, num_classes * 3].
const float * reg_logits
Shape [num_pillars, num_classes * reg_dim].
std::vector< float > score_thresholds
Per-class thresholds, or one shared threshold.

References pcod_common::BoundingBox::center, pcod_common::PillarGrid::centers, pcod_common::PbodOutputsView::class_logits, pcod_common::BoundingBox::classification, pcod_common::BoundingBox::existence_probability, pcod_common::PbodOutputsView::focal_logits, pcod_common::BoundingBox::height, pcod_common::BoundingBox::length, pcod_common::PbodOutputsView::num_classes, pcod_common::PbodOutputsView::num_pillars, pcod_common::PbodOutputsView::reg_dim, pcod_common::PbodOutputsView::reg_logits, pcod_common::PbodPostprocessConfig::score_thresholds, pcod_common::PbodOutputsView::size_posterior, pcod_common::BoundingBox::width, wrap_to_range(), pcod_common::BoundingBox::yaw, and pcod_common::BoundingBox::z.

Referenced by main().

◆ HasCudaPillarPreprocessSupport()

bool pcod_common::HasCudaPillarPreprocessSupport ( )

Return whether this build and runtime provide CUDA pillar preprocessing.

Definition at line 10 of file pillar_preprocess_cuda_stub.cpp.

10{ return false; }

Referenced by pcod_common::PillarPreprocessCudaContext::isAvailable().

◆ LoadModelManifest()

ModelManifest pcod_common::LoadModelManifest ( const std::string & path)
Parameters
pathManifest YAML path.
Returns
Parsed and validated manifest.

Definition at line 235 of file model_manifest.cpp.

235 {
236 try {
237 const YAML::Node root = YAML::LoadFile(path);
238 EnsureKnownKeys(root, "root", {"schema_version", "artifact", "frozen_contract", "runtime_defaults"});
239
240 const YAML::Node artifact = RequireChild(root, "artifact", "artifact");
241 EnsureKnownKeys(artifact, "artifact",
242 {"bundle_name", "export_format", "backend", "precision", "device", "head_name", "export_timestamp_utc",
243 "files", "triton", "inputs", "outputs", "size_priors", "size_priors_source"});
244
245 const YAML::Node files = RequireChild(artifact, "files", "artifact.files");
246 EnsureKnownKeys(files, "artifact.files",
247 {"model", "checkpoint", "resolved_training_config", "triton_repository", "triton_config", "triton_model"});
248
249 const YAML::Node triton = RequireChild(artifact, "triton", "artifact.triton");
250 EnsureKnownKeys(triton, "artifact.triton", {"enabled", "model_name", "model_version"});
251
252 const YAML::Node inputs = RequireChild(artifact, "inputs", "artifact.inputs");
253 const YAML::Node outputs = RequireChild(artifact, "outputs", "artifact.outputs");
254
255 const YAML::Node frozen_contract = RequireChild(root, "frozen_contract", "frozen_contract");
256 EnsureKnownKeys(frozen_contract, "frozen_contract", {"preprocessing", "postprocessing", "model"});
257
258 const YAML::Node preprocessing = RequireChild(frozen_contract, "preprocessing", "frozen_contract.preprocessing");
259 EnsureKnownKeys(preprocessing, "frozen_contract.preprocessing",
260 {"max_num_points", "num_point_features", "point_cloud_range", "voxel_size", "point_feature_normalization"});
261
262 const YAML::Node point_cloud_range =
263 RequireChild(preprocessing, "point_cloud_range", "frozen_contract.preprocessing.point_cloud_range");
264 EnsureKnownKeys(point_cloud_range, "frozen_contract.preprocessing.point_cloud_range", {"x", "y", "z"});
265
266 const YAML::Node voxel_size = RequireChild(preprocessing, "voxel_size", "frozen_contract.preprocessing.voxel_size");
267 EnsureKnownKeys(voxel_size, "frozen_contract.preprocessing.voxel_size", {"x", "y", "z"});
268
269 const YAML::Node normalization =
270 RequireChild(preprocessing, "point_feature_normalization", "frozen_contract.preprocessing.point_feature_normalization");
271 EnsureKnownKeys(normalization, "frozen_contract.preprocessing.point_feature_normalization",
272 {"type", "min_value", "max_value", "epsilon"});
273
274 const YAML::Node postprocessing = RequireChild(frozen_contract, "postprocessing", "frozen_contract.postprocessing");
275 EnsureKnownKeys(postprocessing, "frozen_contract.postprocessing", {"grid_size", "num_classes", "class_names"});
276
277 const YAML::Node grid_size = RequireChild(postprocessing, "grid_size", "frozen_contract.postprocessing.grid_size");
278 EnsureKnownKeys(grid_size, "frozen_contract.postprocessing.grid_size", {"x", "y"});
279
280 const YAML::Node model = RequireChild(frozen_contract, "model", "frozen_contract.model");
281 EnsureKnownKeys(model, "frozen_contract.model",
282 {"stride", "up_stride", "first_up_stride", "pillar_map_size", "pillar_map_range"});
283
284 const YAML::Node runtime_defaults = RequireChild(root, "runtime_defaults", "runtime_defaults");
285 EnsureKnownKeys(runtime_defaults, "runtime_defaults", {"preprocessing", "postprocessing"});
286
287 const YAML::Node runtime_preprocessing = RequireChild(runtime_defaults, "preprocessing", "runtime_defaults.preprocessing");
288 EnsureKnownKeys(runtime_preprocessing, "runtime_defaults.preprocessing", {"point_feature"});
289
290 const YAML::Node point_feature =
291 RequireChild(runtime_preprocessing, "point_feature", "runtime_defaults.preprocessing.point_feature");
292 EnsureKnownKeys(point_feature, "runtime_defaults.preprocessing.point_feature", {"value_threshold"});
293
294 const YAML::Node runtime_postprocessing = RequireChild(runtime_defaults, "postprocessing", "runtime_defaults.postprocessing");
295 EnsureKnownKeys(runtime_postprocessing, "runtime_defaults.postprocessing", {"class_score_threshold", "nms"});
296
297 const YAML::Node nms = RequireChild(runtime_postprocessing, "nms", "runtime_defaults.postprocessing.nms");
298 EnsureKnownKeys(nms, "runtime_defaults.postprocessing.nms", {"score_threshold", "iou_threshold", "max_num_objects"});
299
300 ModelManifest manifest;
301
302 manifest.schema_version = RequireScalarText(RequireChild(root, "schema_version", "schema_version"), "schema_version");
303
304 manifest.artifact.bundle_name =
305 RequireScalarText(RequireChild(artifact, "bundle_name", "artifact.bundle_name"), "artifact.bundle_name");
306 manifest.artifact.export_format =
307 RequireScalarText(RequireChild(artifact, "export_format", "artifact.export_format"), "artifact.export_format");
308 manifest.artifact.backend = RequireScalarText(RequireChild(artifact, "backend", "artifact.backend"), "artifact.backend");
309 manifest.artifact.precision =
310 RequireScalarText(RequireChild(artifact, "precision", "artifact.precision"), "artifact.precision");
311 manifest.artifact.device = RequireScalarText(RequireChild(artifact, "device", "artifact.device"), "artifact.device");
312 manifest.artifact.head_name = LoadOptionalText(LoadOptionalChild(artifact, "head_name"), "artifact.head_name");
313 manifest.artifact.export_timestamp_utc =
314 LoadOptionalText(LoadOptionalChild(artifact, "export_timestamp_utc"), "artifact.export_timestamp_utc");
315
316 manifest.artifact.files.model = LoadOptionalText(LoadOptionalChild(files, "model"), "artifact.files.model");
317 manifest.artifact.files.checkpoint =
318 RequireScalarText(RequireChild(files, "checkpoint", "artifact.files.checkpoint"), "artifact.files.checkpoint");
319 manifest.artifact.files.resolved_training_config =
320 RequireScalarText(RequireChild(files, "resolved_training_config", "artifact.files.resolved_training_config"),
321 "artifact.files.resolved_training_config");
322 manifest.artifact.files.triton_repository =
323 LoadOptionalText(LoadOptionalChild(files, "triton_repository"), "artifact.files.triton_repository");
324 manifest.artifact.files.triton_config =
325 LoadOptionalText(LoadOptionalChild(files, "triton_config"), "artifact.files.triton_config");
326 manifest.artifact.files.triton_model =
327 LoadOptionalText(LoadOptionalChild(files, "triton_model"), "artifact.files.triton_model");
328
329 manifest.artifact.triton.enabled =
330 RequireScalarValue<bool>(RequireChild(triton, "enabled", "artifact.triton.enabled"), "artifact.triton.enabled");
331 manifest.artifact.triton.model_name = LoadOptionalText(LoadOptionalChild(triton, "model_name"), "artifact.triton.model_name");
332 manifest.artifact.triton.model_version =
333 LoadOptionalText(LoadOptionalChild(triton, "model_version"), "artifact.triton.model_version");
334
335 manifest.artifact.inputs = RequireTensorList(inputs, "artifact.inputs");
336 manifest.artifact.outputs = RequireTensorList(outputs, "artifact.outputs");
337 manifest.artifact.size_priors = LoadOptionalSizePriors(LoadOptionalChild(artifact, "size_priors"), "artifact.size_priors");
338 manifest.artifact.size_priors_source =
339 LoadOptionalText(LoadOptionalChild(artifact, "size_priors_source"), "artifact.size_priors_source");
340
341 manifest.frozen_contract.preprocessing.max_num_points =
342 RequireScalarValue<int>(RequireChild(preprocessing, "max_num_points", "frozen_contract.preprocessing.max_num_points"),
343 "frozen_contract.preprocessing.max_num_points");
344 manifest.frozen_contract.preprocessing.num_point_features = RequireScalarValue<int>(
345 RequireChild(preprocessing, "num_point_features", "frozen_contract.preprocessing.num_point_features"),
346 "frozen_contract.preprocessing.num_point_features");
347 manifest.frozen_contract.preprocessing.x_range =
348 RequireRange2(RequireChild(point_cloud_range, "x", "frozen_contract.preprocessing.point_cloud_range.x"),
349 "frozen_contract.preprocessing.point_cloud_range.x");
350 manifest.frozen_contract.preprocessing.y_range =
351 RequireRange2(RequireChild(point_cloud_range, "y", "frozen_contract.preprocessing.point_cloud_range.y"),
352 "frozen_contract.preprocessing.point_cloud_range.y");
353 manifest.frozen_contract.preprocessing.z_range =
354 RequireRange2(RequireChild(point_cloud_range, "z", "frozen_contract.preprocessing.point_cloud_range.z"),
355 "frozen_contract.preprocessing.point_cloud_range.z");
356 manifest.frozen_contract.preprocessing.voxel_x =
357 RequireScalarValue<float>(RequireChild(voxel_size, "x", "frozen_contract.preprocessing.voxel_size.x"),
358 "frozen_contract.preprocessing.voxel_size.x");
359 manifest.frozen_contract.preprocessing.voxel_y =
360 RequireScalarValue<float>(RequireChild(voxel_size, "y", "frozen_contract.preprocessing.voxel_size.y"),
361 "frozen_contract.preprocessing.voxel_size.y");
362 manifest.frozen_contract.preprocessing.voxel_z =
363 RequireScalarValue<float>(RequireChild(voxel_size, "z", "frozen_contract.preprocessing.voxel_size.z"),
364 "frozen_contract.preprocessing.voxel_size.z");
365 manifest.frozen_contract.preprocessing.point_feature_normalization.type =
366 RequireScalarText(RequireChild(normalization, "type", "frozen_contract.preprocessing.point_feature_normalization.type"),
367 "frozen_contract.preprocessing.point_feature_normalization.type");
368 manifest.frozen_contract.preprocessing.point_feature_normalization.epsilon = RequireScalarValue<float>(
369 RequireChild(normalization, "epsilon", "frozen_contract.preprocessing.point_feature_normalization.epsilon"),
370 "frozen_contract.preprocessing.point_feature_normalization.epsilon");
371 if (manifest.frozen_contract.preprocessing.point_feature_normalization.type == "min_max") {
372 manifest.frozen_contract.preprocessing.point_feature_normalization.min_value = RequireScalarValue<float>(
373 RequireChild(normalization, "min_value", "frozen_contract.preprocessing.point_feature_normalization.min_value"),
374 "frozen_contract.preprocessing.point_feature_normalization.min_value");
375 manifest.frozen_contract.preprocessing.point_feature_normalization.max_value = RequireScalarValue<float>(
376 RequireChild(normalization, "max_value", "frozen_contract.preprocessing.point_feature_normalization.max_value"),
377 "frozen_contract.preprocessing.point_feature_normalization.max_value");
378 }
379
380 manifest.frozen_contract.postprocessing.grid_x = RequireScalarValue<int>(
381 RequireChild(grid_size, "x", "frozen_contract.postprocessing.grid_size.x"), "frozen_contract.postprocessing.grid_size.x");
382 manifest.frozen_contract.postprocessing.grid_y = RequireScalarValue<int>(
383 RequireChild(grid_size, "y", "frozen_contract.postprocessing.grid_size.y"), "frozen_contract.postprocessing.grid_size.y");
384 manifest.frozen_contract.postprocessing.num_classes =
385 RequireScalarValue<int>(RequireChild(postprocessing, "num_classes", "frozen_contract.postprocessing.num_classes"),
386 "frozen_contract.postprocessing.num_classes");
387 manifest.frozen_contract.postprocessing.class_names =
388 RequireStringSequence(RequireChild(postprocessing, "class_names", "frozen_contract.postprocessing.class_names"),
389 "frozen_contract.postprocessing.class_names");
390
391 manifest.frozen_contract.model.stride =
392 RequirePositiveIntSequence(RequireChild(model, "stride", "frozen_contract.model.stride"), "frozen_contract.model.stride");
393 manifest.frozen_contract.model.up_stride = RequirePositiveIntSequence(
394 RequireChild(model, "up_stride", "frozen_contract.model.up_stride"), "frozen_contract.model.up_stride");
395 manifest.frozen_contract.model.first_up_stride = RequireScalarValue<int>(
396 RequireChild(model, "first_up_stride", "frozen_contract.model.first_up_stride"), "frozen_contract.model.first_up_stride");
397
398 {
399 const YAML::Node pillar_map_size = RequireChild(model, "pillar_map_size", "frozen_contract.model.pillar_map_size");
400 EnsureSequence(pillar_map_size, "frozen_contract.model.pillar_map_size");
401 if (pillar_map_size.size() != 2) {
402 throw std::runtime_error("Manifest field 'frozen_contract.model.pillar_map_size' must be a 2-element sequence");
403 }
404 manifest.frozen_contract.model.pillar_map_size = {pillar_map_size[0].as<int>(), pillar_map_size[1].as<int>()};
405 }
406
407 {
408 const YAML::Node pillar_map_range = RequireChild(model, "pillar_map_range", "frozen_contract.model.pillar_map_range");
409 EnsureSequence(pillar_map_range, "frozen_contract.model.pillar_map_range");
410 if (pillar_map_range.size() != 3) {
411 throw std::runtime_error("Manifest field 'frozen_contract.model.pillar_map_range' must contain three ranges");
412 }
413 manifest.frozen_contract.model.pillar_map_range = {
414 RequireRange2(pillar_map_range[0], "frozen_contract.model.pillar_map_range[0]"),
415 RequireRange2(pillar_map_range[1], "frozen_contract.model.pillar_map_range[1]"),
416 RequireRange2(pillar_map_range[2], "frozen_contract.model.pillar_map_range[2]")};
417 }
418
419 manifest.runtime_defaults.preprocessing.point_feature.value_threshold =
420 LoadOptionalScalarValue<float>(LoadOptionalChild(point_feature, "value_threshold"),
421 "runtime_defaults.preprocessing.point_feature.value_threshold", 0.0F);
422 manifest.runtime_defaults.postprocessing.class_score_threshold = RequireScalarValue<float>(
423 RequireChild(runtime_postprocessing, "class_score_threshold", "runtime_defaults.postprocessing.class_score_threshold"),
424 "runtime_defaults.postprocessing.class_score_threshold");
425 manifest.runtime_defaults.postprocessing.nms_score_thresholds =
426 RequireScoreThresholds(RequireChild(nms, "score_threshold", "runtime_defaults.postprocessing.nms.score_threshold"),
427 "runtime_defaults.postprocessing.nms.score_threshold");
428 manifest.runtime_defaults.postprocessing.nms_iou_threshold =
429 RequireScalarValue<float>(RequireChild(nms, "iou_threshold", "runtime_defaults.postprocessing.nms.iou_threshold"),
430 "runtime_defaults.postprocessing.nms.iou_threshold");
431 manifest.runtime_defaults.postprocessing.max_detections =
432 RequireScalarValue<int>(RequireChild(nms, "max_num_objects", "runtime_defaults.postprocessing.nms.max_num_objects"),
433 "runtime_defaults.postprocessing.nms.max_num_objects");
434
435 ValidateModelManifest(manifest);
436 return manifest;
437 } catch (const YAML::Exception& exc) {
438 throw std::runtime_error("Failed to parse manifest '" + path + "': " + std::string(exc.what()));
439 }
440}
void ValidateModelManifest(const ModelManifest &manifest)

References pcod_common::ModelManifest::schema_version, and ValidateModelManifest().

Referenced by main().

◆ ParsePointFeatureNormalizationType()

PointFeatureNormalizationType pcod_common::ParsePointFeatureNormalizationType ( const std::string & value)
Parameters
valueManifest normalization name.
Returns
Parsed strategy.
Exceptions
std::invalid_argumentfor unknown names.

Definition at line 10 of file point_preprocess.cpp.

10 {
11 if (value == "none") {
12 return PointFeatureNormalizationType::kNone;
13 }
14 if (value == "value_threshold") {
15 return PointFeatureNormalizationType::kValueThreshold;
16 }
17 if (value == "min_max") {
18 return PointFeatureNormalizationType::kMinMax;
19 }
20 if (value == "z_score") {
21 return PointFeatureNormalizationType::kZScore;
22 }
23 throw std::runtime_error("Unsupported point feature normalization type: " + value);
24}

References kMinMax, kNone, kValueThreshold, and kZScore.

Referenced by main().

◆ scale_score()

template<typename T >
T pcod_common::scale_score ( T score,
T old_thresh,
T new_thresh )

Remap a confidence score while preserving zero and one.

Parameters
scoreInput confidence.
old_threshExisting threshold pivot.
new_threshDesired threshold pivot.
Returns
Rescaled confidence.

Definition at line 23 of file math.hpp.

23 {
24 const T one = static_cast<T>(1);
25 if (score <= old_thresh) {
26 return score * new_thresh / old_thresh;
27 }
28 return one - (one - score) * (one - new_thresh) / (one - old_thresh);
29}

Referenced by ApplyRotatedNms().

◆ ValidateModelManifest()

void pcod_common::ValidateModelManifest ( const ModelManifest & manifest)
Parameters
manifestManifest to validate.
Exceptions
std::runtime_errorfor contract violations.

Definition at line 442 of file model_manifest.cpp.

442 {
443 if (manifest.schema_version.empty()) {
444 throw std::runtime_error("Manifest schema_version is required");
445 }
447 throw std::runtime_error("Unsupported manifest schema_version: " + manifest.schema_version);
448 }
449
450 RequireNonEmptyString(manifest.artifact.bundle_name, "artifact.bundle_name");
451 RequireNonEmptyString(manifest.artifact.export_format, "artifact.export_format");
452 RequireNonEmptyString(manifest.artifact.backend, "artifact.backend");
453 RequireNonEmptyString(manifest.artifact.precision, "artifact.precision");
454 RequireNonEmptyString(manifest.artifact.device, "artifact.device");
455 if (!manifest.artifact.head_name.empty()) {
456 RequireNonEmptyString(manifest.artifact.head_name, "artifact.head_name");
457 }
458 if (!manifest.artifact.export_timestamp_utc.empty()) {
459 RequireNonEmptyString(manifest.artifact.export_timestamp_utc, "artifact.export_timestamp_utc");
460 }
461
462 if (!manifest.artifact.files.model.empty()) {
463 RequireBundleRelativePath(manifest.artifact.files.model, "artifact.files.model");
464 }
465 RequireBundleRelativePath(manifest.artifact.files.checkpoint, "artifact.files.checkpoint");
466 RequireBundleRelativePath(manifest.artifact.files.resolved_training_config, "artifact.files.resolved_training_config");
467 if (!manifest.artifact.files.triton_repository.empty()) {
468 RequireBundleRelativePath(manifest.artifact.files.triton_repository, "artifact.files.triton_repository");
469 }
470 if (!manifest.artifact.files.triton_config.empty()) {
471 RequireBundleRelativePath(manifest.artifact.files.triton_config, "artifact.files.triton_config");
472 }
473 if (!manifest.artifact.files.triton_model.empty()) {
474 RequireBundleRelativePath(manifest.artifact.files.triton_model, "artifact.files.triton_model");
475 }
476
477 if (manifest.artifact.triton.enabled) {
478 RequireNonEmptyString(manifest.artifact.triton.model_name, "artifact.triton.model_name");
479 RequireNonEmptyString(manifest.artifact.triton.model_version, "artifact.triton.model_version");
480 if (manifest.artifact.files.triton_repository.empty() || manifest.artifact.files.triton_config.empty() ||
481 manifest.artifact.files.triton_model.empty()) {
482 throw std::runtime_error("artifact.files must declare Triton repository, config, and model paths");
483 }
484 } else if (manifest.artifact.files.model.empty()) {
485 throw std::runtime_error("artifact.files.model is required for non-Triton exports");
486 }
487
488 if (manifest.artifact.inputs.empty()) {
489 throw std::runtime_error("artifact.inputs must not be empty");
490 }
491 for (std::size_t i = 0; i < manifest.artifact.inputs.size(); ++i) {
492 const auto& tensor = manifest.artifact.inputs[i];
493 if (tensor.name.empty() || tensor.dtype.empty() || tensor.shape.empty()) {
494 throw std::runtime_error("artifact.inputs[" + std::to_string(i) + "] must define name, dtype, and shape");
495 }
496 }
497 if (manifest.artifact.outputs.empty()) {
498 throw std::runtime_error("artifact.outputs must not be empty");
499 }
500 for (std::size_t i = 0; i < manifest.artifact.outputs.size(); ++i) {
501 const auto& tensor = manifest.artifact.outputs[i];
502 if (tensor.name.empty() || tensor.dtype.empty() || tensor.shape.empty()) {
503 throw std::runtime_error("artifact.outputs[" + std::to_string(i) + "] must define name, dtype, and shape");
504 }
505 }
506
507 if (manifest.frozen_contract.preprocessing.max_num_points <= 0) {
508 throw std::runtime_error("frozen_contract.preprocessing.max_num_points must be > 0");
509 }
511 throw std::runtime_error("frozen_contract.preprocessing.num_point_features must be > 0");
512 }
513 if (manifest.frozen_contract.postprocessing.num_classes <= 0) {
514 throw std::runtime_error("frozen_contract.postprocessing.num_classes must be > 0");
515 }
516 if (manifest.frozen_contract.postprocessing.class_names.size() !=
517 static_cast<std::size_t>(manifest.frozen_contract.postprocessing.num_classes)) {
518 throw std::runtime_error("frozen_contract.postprocessing.class_names must contain one entry per class");
519 }
520 if (manifest.frozen_contract.model.pillar_map_size[0] <= 0 || manifest.frozen_contract.model.pillar_map_size[1] <= 0) {
521 throw std::runtime_error("frozen_contract.model.pillar_map_size must be positive");
522 }
523 if (manifest.frozen_contract.model.stride.empty()) {
524 throw std::runtime_error("frozen_contract.model.stride must not be empty");
525 }
526 if (manifest.frozen_contract.model.up_stride.empty()) {
527 throw std::runtime_error("frozen_contract.model.up_stride must not be empty");
528 }
529 if (manifest.frozen_contract.model.first_up_stride <= 0) {
530 throw std::runtime_error("frozen_contract.model.first_up_stride must be > 0");
531 }
533 throw std::runtime_error("frozen_contract.preprocessing.point_feature_normalization.epsilon must be > 0");
534 }
535
537 if (norm.type == "value_threshold") {
539 throw std::runtime_error(
540 "runtime_defaults.preprocessing.point_feature.value_threshold must be > 0 when value_threshold normalization is used");
541 }
542 } else if (norm.type == "min_max") {
543 if (!(norm.min_value < norm.max_value)) {
544 throw std::runtime_error("frozen_contract.preprocessing.point_feature_normalization requires min_value < max_value");
545 }
546 } else if (norm.type == "z_score") {
547 // ok
548 } else if (norm.type != "none") {
549 throw std::runtime_error("frozen_contract.preprocessing.point_feature_normalization.type is invalid");
550 }
551
554 throw std::runtime_error("runtime_defaults.postprocessing.class_score_threshold must be within [0.0, 1.0]");
555 }
558 throw std::runtime_error("runtime_defaults.postprocessing.nms.iou_threshold must be within [0.0, 1.0]");
559 }
561 throw std::runtime_error("runtime_defaults.postprocessing.nms.max_num_objects must be zero or positive");
562 }
564 throw std::runtime_error("runtime_defaults.postprocessing.nms.score_threshold must not be empty");
565 }
566 const std::size_t score_count = manifest.runtime_defaults.postprocessing.nms_score_thresholds.size();
567 if (score_count != 1 && score_count != static_cast<std::size_t>(manifest.frozen_contract.postprocessing.num_classes)) {
568 throw std::runtime_error("runtime_defaults.postprocessing.nms.score_threshold must contain one value or one value per class");
569 }
570 for (float threshold : manifest.runtime_defaults.postprocessing.nms_score_thresholds) {
571 if (threshold < 0.0F || threshold > 1.0F) {
572 throw std::runtime_error("runtime_defaults.postprocessing.nms.score_threshold entries must be within [0.0, 1.0]");
573 }
574 }
575}
constexpr const char * kManifestSchemaVersion
Definition version.hpp:10
std::string triton_config
Triton model configuration path.
std::string triton_model
Triton model artifact path.
std::string model
Exported model path.
std::string checkpoint
Source checkpoint path.
std::string resolved_training_config
Resolved training configuration path.
std::string triton_repository
Triton repository path.
std::string model_version
Triton model version.
bool enabled
Whether Triton artifacts were exported.
std::string export_timestamp_utc
UTC export timestamp.
std::string head_name
Detection head identifier.
std::vector< Tensor > outputs
Ordered output tensor contracts.
std::string precision
Numeric precision.
std::string bundle_name
Human-readable bundle name.
std::string device
Export target device.
TritonDeployment triton
Triton deployment metadata.
std::string export_format
Model serialization format.
std::string backend
Runtime backend.
Files files
Bundle-relative files.
std::vector< Tensor > inputs
Ordered input tensor contracts.
FrozenModelConfig model
Model architecture contract.
FrozenPreprocessConfig preprocessing
Preprocessing contract.
FrozenPostprocessConfig postprocessing
Postprocessing contract.
std::array< int, 2 > pillar_map_size
Base pillar-map dimensions.
int first_up_stride
First decoder upsampling factor.
std::vector< int > stride
Backbone strides.
std::vector< int > up_stride
Decoder upsampling strides.
std::vector< std::string > class_names
Class names in output order.
int num_classes
Number of semantic classes.
PointFeatureNormalizationContract point_feature_normalization
Scalar feature contract.
int num_point_features
Features expected per point.
int max_num_points
Maximum input point count.
RuntimeDefaults runtime_defaults
Overridable runtime defaults.
std::string schema_version
Manifest schema version.
FrozenContract frozen_contract
Non-overridable inference contract.
ArtifactConfig artifact
Artifact metadata.
int max_detections
Maximum final detection count.
float nms_iou_threshold
Rotated NMS IoU threshold.
std::vector< float > nms_score_thresholds
NMS class score thresholds.
float class_score_threshold
Decoder confidence threshold.
float value_threshold
Value-threshold normalization divisor.
PointFeature point_feature
Additional point-feature defaults.
Postprocessing postprocessing
Postprocessing defaults.
Preprocessing preprocessing
Preprocessing defaults.

References pcod_common::ModelManifest::artifact, pcod_common::ArtifactConfig::backend, pcod_common::ArtifactConfig::bundle_name, pcod_common::ArtifactConfig::Files::checkpoint, pcod_common::FrozenPostprocessConfig::class_names, pcod_common::RuntimeDefaults::Postprocessing::class_score_threshold, pcod_common::ArtifactConfig::device, pcod_common::ArtifactConfig::TritonDeployment::enabled, pcod_common::FrozenPreprocessConfig::PointFeatureNormalizationContract::epsilon, pcod_common::ArtifactConfig::export_format, pcod_common::ArtifactConfig::export_timestamp_utc, pcod_common::ArtifactConfig::files, pcod_common::FrozenModelConfig::first_up_stride, pcod_common::ModelManifest::frozen_contract, pcod_common::ArtifactConfig::head_name, pcod_common::ArtifactConfig::inputs, kManifestSchemaVersion, pcod_common::RuntimeDefaults::Postprocessing::max_detections, pcod_common::FrozenPreprocessConfig::max_num_points, pcod_common::ArtifactConfig::Files::model, pcod_common::FrozenContract::model, pcod_common::ArtifactConfig::TritonDeployment::model_name, pcod_common::ArtifactConfig::TritonDeployment::model_version, pcod_common::RuntimeDefaults::Postprocessing::nms_iou_threshold, pcod_common::RuntimeDefaults::Postprocessing::nms_score_thresholds, pcod_common::FrozenPostprocessConfig::num_classes, pcod_common::FrozenPreprocessConfig::num_point_features, pcod_common::ArtifactConfig::outputs, pcod_common::FrozenModelConfig::pillar_map_size, pcod_common::RuntimeDefaults::Preprocessing::point_feature, pcod_common::FrozenPreprocessConfig::point_feature_normalization, pcod_common::FrozenContract::postprocessing, pcod_common::RuntimeDefaults::postprocessing, pcod_common::ArtifactConfig::precision, pcod_common::FrozenContract::preprocessing, pcod_common::RuntimeDefaults::preprocessing, pcod_common::ArtifactConfig::Files::resolved_training_config, pcod_common::ModelManifest::runtime_defaults, pcod_common::ModelManifest::schema_version, pcod_common::FrozenModelConfig::stride, pcod_common::ArtifactConfig::triton, pcod_common::ArtifactConfig::Files::triton_config, pcod_common::ArtifactConfig::Files::triton_model, pcod_common::ArtifactConfig::Files::triton_repository, pcod_common::FrozenModelConfig::up_stride, and pcod_common::RuntimeDefaults::Preprocessing::PointFeature::value_threshold.

Referenced by LoadModelManifest(), and main().

◆ wrap_to_range()

float pcod_common::wrap_to_range ( float val,
float min_val,
float max_val )
inline
Parameters
valInput value.
min_valLower bound.
max_valUpper bound.
Returns
Wrapped value.

Definition at line 32 of file math.hpp.

32 {
33 float range = max_val - min_val;
34 return val - range * std::floor((val - min_val) / range);
35}

Referenced by DecodePbod().

Variable Documentation

◆ kManifestSchemaVersion

const char* pcod_common::kManifestSchemaVersion = "2.0"
inlineconstexpr

Supported model-manifest schema version.

Definition at line 10 of file version.hpp.

Referenced by main(), and ValidateModelManifest().

◆ kVersion

const char* pcod_common::kVersion = "1.0.0"
inlineconstexpr

Library release version.

Definition at line 8 of file version.hpp.