Skip to content

Commit f55c842

Browse files
authored
feat: Enable generic attributes for point cloud encode/decode (#61)
* Ensure attribute `unique_id`s are always unique * Enable generic attributes for point clouds in addition to meshes * Check that all `unique_ids` are >= -1 * Raise errors instead of silently failing on invalid input for encoding
1 parent 319f664 commit f55c842

File tree

4 files changed

+143
-28
lines changed

4 files changed

+143
-28
lines changed

src/DracoPy.h

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include<cmath>
66
#include<vector>
77
#include<cstddef>
8+
#include<sstream>
89
#include "draco/compression/decode.h"
910
#include "draco/compression/encode.h"
1011
#include "draco/compression/config/compression_shared.h"
@@ -291,6 +292,17 @@ namespace DracoFunctions {
291292
// manually.
292293
draco::Mesh mesh; //Initialize a draco mesh
293294

295+
// Ensure unique ids for native attributes don't conflict with generic attributes
296+
uint32_t next_unique_id = 0;
297+
auto min_unique_id = std::min_element(unique_ids.begin(), unique_ids.end());
298+
if (min_unique_id != unique_ids.end() && *min_unique_id < -1) {
299+
throw std::invalid_argument("Should never be reached; all unique_ids should be >= -1.");
300+
}
301+
auto max_unique_id = std::max_element(unique_ids.begin(), unique_ids.end());
302+
if (max_unique_id != unique_ids.end()) {
303+
next_unique_id = *max_unique_id + 1;
304+
}
305+
294306
// Process vertices
295307
const size_t num_pts = points.size() / 3;
296308
mesh.set_num_points(num_pts);
@@ -331,6 +343,8 @@ namespace DracoFunctions {
331343
sizeof(uint8_t) * colors_channel, // byte stride
332344
0); // byte offset
333345
color_att_id = mesh.AddAttribute(colors_attr, true, num_pts);
346+
mesh.attribute(color_att_id)->set_unique_id(next_unique_id);
347+
next_unique_id++;
334348
}
335349
int tex_coord_att_id = -1;
336350
if(tex_coord_channel) {
@@ -343,6 +357,8 @@ namespace DracoFunctions {
343357
sizeof(float) * tex_coord_channel, // byte stride
344358
0); // byte offset
345359
tex_coord_att_id = mesh.AddAttribute(tex_coord_attr, true, num_pts);
360+
mesh.attribute(tex_coord_att_id)->set_unique_id(next_unique_id);
361+
next_unique_id++;
346362
}
347363

348364
int normal_att_id = -1;
@@ -356,6 +372,8 @@ namespace DracoFunctions {
356372
sizeof(float) * 3, // byte stride
357373
0); // byte offset
358374
normal_att_id = mesh.AddAttribute(normal_attr, true, num_pts);
375+
mesh.attribute(normal_att_id)->set_unique_id(next_unique_id);
376+
next_unique_id++;
359377
}
360378

361379

@@ -369,21 +387,22 @@ namespace DracoFunctions {
369387
draco::DataType dtype = static_cast<draco::DataType>(attr_data_types[i]);
370388
int num_components = attr_num_components[i];
371389
if (dtype != draco::DT_FLOAT32 && dtype != draco::DT_UINT8 && dtype != draco::DT_UINT16 && dtype != draco::DT_UINT32) {
372-
// Unsupported data type, skip
373-
// std::cout << "DEBUG: Unsupported attribute data type for " << unique_ids[i] << std::endl;
374-
generic_attr_ids.push_back(-1);
375-
continue;
390+
std::ostringstream oss;
391+
oss << "Unsupported attribute data type for attribute with unique id " << int(unique_ids[i]);
392+
throw std::invalid_argument(oss.str());
376393
}
377394
generic_attr.Init(draco::GeometryAttribute::GENERIC, nullptr, num_components, dtype, false, 0, 0);
378395
int att_id = mesh.AddAttribute(generic_attr, true, num_pts);
379396
if (att_id == -1) {
380-
// Failed to add attribute, skip
381-
// std::cout << "DEBUG: Failed to add attribute " << unique_ids[i] << std::endl;
382-
generic_attr_ids.push_back(-1);
383-
continue;
397+
std::ostringstream oss;
398+
oss << "Failed to add attribute with unique id " << int(unique_ids[i]);
399+
throw std::runtime_error(oss.str());
384400
}
385401
if (unique_ids[i] >= 0) {
386402
mesh.attribute(att_id)->set_unique_id(unique_ids[i]);
403+
} else {
404+
mesh.attribute(att_id)->set_unique_id(next_unique_id);
405+
next_unique_id++;
387406
}
388407
if (!attr_names[i].empty()) {
389408
auto attribute_metadata = std::unique_ptr<draco::AttributeMetadata>(new draco::AttributeMetadata());
@@ -396,6 +415,8 @@ namespace DracoFunctions {
396415

397416

398417
const int pos_att_id = mesh.AddAttribute(positions_attr, true, num_pts);
418+
mesh.attribute(pos_att_id)->set_unique_id(next_unique_id);
419+
next_unique_id++;
399420
std::vector<int32_t> pts_int32;
400421
std::vector<uint32_t> pts_uint32;
401422
if (integer_mark == 1) {
@@ -439,8 +460,7 @@ namespace DracoFunctions {
439460
const auto &uint32_data = attr_uint32_data[j];
440461

441462
if (generic_attr_ids[j] == -1) {
442-
// Skip if attribute was not added
443-
continue;
463+
throw std::runtime_error("Should never be reached; all successfully added attributes should have nonnegative att_id.");
444464
}
445465

446466
if (attr_data_types[j] == draco::DT_FLOAT32) {
@@ -500,12 +520,31 @@ namespace DracoFunctions {
500520
const float *quantization_origin, const bool preserve_order,
501521
const bool create_metadata, const int integer_mark,
502522
const std::vector<uint8_t> &colors,
503-
const uint8_t colors_channel
523+
const uint8_t colors_channel,
524+
std::vector<int8_t>& unique_ids,
525+
std::vector<std::vector<float>>& attr_float_data,
526+
std::vector<std::vector<uint8_t>>& attr_uint8_data,
527+
std::vector<std::vector<uint16_t>>& attr_uint16_data,
528+
std::vector<std::vector<uint32_t>>& attr_uint32_data,
529+
std::vector<int>& attr_data_types,
530+
std::vector<int>& attr_num_components,
531+
std::vector<std::string>& attr_names
504532
) {
505533
int num_points = points.size() / 3;
506534
draco::PointCloudBuilder pcb;
507535
pcb.Start(num_points);
508536

537+
// Ensure unique ids for native attributes don't conflict with generic attributes
538+
uint32_t next_unique_id = 0;
539+
auto min_unique_id = std::min_element(unique_ids.begin(), unique_ids.end());
540+
if (min_unique_id != unique_ids.end() && *min_unique_id < -1) {
541+
throw std::invalid_argument("Should never be reached; all unique_ids should be >= -1.");
542+
}
543+
auto max_unique_id = std::max_element(unique_ids.begin(), unique_ids.end());
544+
if (max_unique_id != unique_ids.end()) {
545+
next_unique_id = *max_unique_id + 1;
546+
}
547+
509548
auto dtype = (integer_mark == 1)
510549
? draco::DataType::DT_INT32
511550
: (
@@ -517,11 +556,15 @@ namespace DracoFunctions {
517556
const int pos_att_id = pcb.AddAttribute(
518557
draco::GeometryAttribute::POSITION, 3, dtype
519558
);
559+
pcb.SetAttributeUniqueId(pos_att_id, next_unique_id);
560+
next_unique_id++;
520561

521562
if(colors_channel){
522563
const int color_att_id = pcb.AddAttribute(
523564
draco::GeometryAttribute::COLOR, colors_channel, draco::DataType::DT_UINT8
524565
);
566+
pcb.SetAttributeUniqueId(color_att_id, next_unique_id);
567+
next_unique_id++;
525568
for (draco::PointIndex i(0); i < num_points; i++) {
526569
pcb.SetAttributeValueForPoint(pos_att_id, i, points.data() + 3 * i.value());
527570
pcb.SetAttributeValueForPoint(color_att_id, i, colors.data() + colors_channel * i.value());
@@ -532,6 +575,44 @@ namespace DracoFunctions {
532575
}
533576
}
534577

578+
// GENERIC ATTRIBUTES
579+
for (size_t j = 0; j < unique_ids.size(); ++j) {
580+
draco::DataType dtype = static_cast<draco::DataType>(attr_data_types[j]);
581+
int num_components = attr_num_components[j];
582+
if (dtype != draco::DT_FLOAT32 && dtype != draco::DT_UINT8 && dtype != draco::DT_UINT16 && dtype != draco::DT_UINT32) {
583+
std::ostringstream oss;
584+
oss << "Unsupported attribute data type for attribute with unique id " << int(unique_ids[j]);
585+
throw std::invalid_argument(oss.str());
586+
}
587+
int att_id = pcb.AddAttribute(draco::GeometryAttribute::GENERIC, num_components, dtype);
588+
if (att_id == -1) {
589+
std::ostringstream oss;
590+
oss << "Failed to add attribute with unique id " << int(unique_ids[j]);
591+
throw std::runtime_error(oss.str());
592+
}
593+
if (unique_ids[j] >= 0) {
594+
pcb.SetAttributeUniqueId(att_id, unique_ids[j]);
595+
} else {
596+
pcb.SetAttributeUniqueId(att_id, next_unique_id);
597+
next_unique_id++;
598+
}
599+
if (!attr_names[j].empty()) {
600+
auto attribute_metadata = std::unique_ptr<draco::AttributeMetadata>(new draco::AttributeMetadata());
601+
attribute_metadata->AddEntryString("name", attr_names[j]);
602+
pcb.AddAttributeMetadata(att_id, std::move(attribute_metadata));
603+
}
604+
605+
if (dtype == draco::DT_FLOAT32) {
606+
pcb.SetAttributeValuesForAllPoints(att_id, attr_float_data[j].data(), 0);
607+
} else if (dtype == draco::DT_UINT8) {
608+
pcb.SetAttributeValuesForAllPoints(att_id, attr_uint8_data[j].data(), 0);
609+
} else if (dtype == draco::DT_UINT16) {
610+
pcb.SetAttributeValuesForAllPoints(att_id, attr_uint16_data[j].data(), 0);
611+
} else if (dtype == draco::DT_UINT32) {
612+
pcb.SetAttributeValuesForAllPoints(att_id, attr_uint32_data[j].data(), 0);
613+
}
614+
}
615+
535616
std::unique_ptr<draco::PointCloud> ptr_point_cloud = pcb.Finalize(!preserve_order);
536617
draco::PointCloud *point_cloud = ptr_point_cloud.get();
537618
draco::Encoder encoder;

src/DracoPy.pxd

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,13 @@ cdef extern from "DracoPy.h" namespace "DracoFunctions":
9595
const bool create_metadata,
9696
const int integer_mark,
9797
const vector[uint8_t] colors,
98-
const uint8_t colors_channel
98+
const uint8_t colors_channel,
99+
vector[int8_t]& unique_ids,
100+
vector[vector[float]]& attr_float_data,
101+
vector[vector[uint8_t]]& attr_uint8_data,
102+
vector[vector[uint16_t]]& attr_uint16_data,
103+
vector[vector[uint32_t]]& attr_uint32_data,
104+
vector[int]& attr_data_types,
105+
vector[int]& attr_num_components,
106+
vector[string]& attr_names
99107
) except +

src/DracoPy.pyx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,11 @@ def encode(
405405
pointsview, quantization_bits, compression_level,
406406
quantization_range, <float*>&quant_origin[0],
407407
preserve_order, create_metadata, integer_mark,
408-
colorsview, colors_channel
408+
colorsview, colors_channel,
409+
unique_ids, attr_float_data, attr_uint8_data,
410+
attr_uint16_data, attr_uint32_data,
411+
attr_data_types, attr_num_components,
412+
attr_names
409413
)
410414
else:
411415
facesview = faces.reshape((faces.size,))

tests.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ def test_normals_encoding():
268268
assert np.allclose(decoded_mesh.normals, test_normals)
269269

270270

271-
def test_generic_attributes():
271+
@pytest.mark.parametrize("object_type", [DracoPy.DracoMesh, DracoPy.DracoPointCloud])
272+
def test_generic_attributes(object_type):
272273
# Read reference mesh
273274
with open(os.path.join(testdata_directory, "bunny.drc"), 'rb') as draco_file:
274275
mesh = DracoPy.decode(draco_file.read())
@@ -282,16 +283,26 @@ def test_generic_attributes():
282283
generic_attributes = {
283284
0: test_tangents,
284285
1: test_joints,
285-
6: test_weights
286+
3: test_weights
286287
}
287-
binary = DracoPy.encode(mesh.points, mesh.faces, generic_attributes=generic_attributes)
288+
if object_type is DracoPy.DracoMesh:
289+
binary = DracoPy.encode(mesh.points, mesh.faces, generic_attributes=generic_attributes)
290+
else:
291+
binary = DracoPy.encode(mesh.points, generic_attributes=generic_attributes)
288292

289293
# Decode and verify attributes
290-
decoded_mesh = DracoPy.decode(binary)
291-
292-
decoded_tangents = decoded_mesh.get_attribute_by_unique_id(0)
293-
decoded_joints = decoded_mesh.get_attribute_by_unique_id(1)
294-
decoded_weights = decoded_mesh.get_attribute_by_unique_id(6)
294+
decoded_object = DracoPy.decode(binary)
295+
if object_type is DracoPy.DracoMesh:
296+
assert type(decoded_object) is DracoPy.DracoMesh
297+
else:
298+
assert type(decoded_object) is DracoPy.DracoPointCloud
299+
300+
assert len(decoded_object.attributes) == len(
301+
set([attr["unique_id"] for attr in decoded_object.attributes])
302+
)
303+
decoded_tangents = decoded_object.get_attribute_by_unique_id(0)
304+
decoded_joints = decoded_object.get_attribute_by_unique_id(1)
305+
decoded_weights = decoded_object.get_attribute_by_unique_id(3)
295306

296307
assert decoded_tangents["attribute_type"] == DracoPy.AttributeType.GENERIC
297308
assert decoded_tangents["data_type"] == DracoPy.DataType.DT_FLOAT32
@@ -309,7 +320,8 @@ def test_generic_attributes():
309320
assert np.allclose(decoded_weights["data"], test_weights)
310321

311322

312-
def test_named_generic_attributes():
323+
@pytest.mark.parametrize("object_type", [DracoPy.DracoMesh, DracoPy.DracoPointCloud])
324+
def test_named_generic_attributes(object_type):
313325
# Read reference mesh
314326
with open(os.path.join(testdata_directory, "bunny.drc"), 'rb') as draco_file:
315327
mesh = DracoPy.decode(draco_file.read())
@@ -325,14 +337,24 @@ def test_named_generic_attributes():
325337
'joints': test_joints,
326338
'weights': test_weights
327339
}
328-
binary = DracoPy.encode(mesh.points, mesh.faces, generic_attributes=generic_attributes)
340+
if object_type is DracoPy.DracoMesh:
341+
binary = DracoPy.encode(mesh.points, mesh.faces, generic_attributes=generic_attributes)
342+
else:
343+
binary = DracoPy.encode(mesh.points, generic_attributes=generic_attributes)
329344

330345
# Decode and verify attributes
331-
decoded_mesh = DracoPy.decode(binary)
332-
333-
decoded_tangents = decoded_mesh.get_attribute_by_name('tangents')
334-
decoded_joints = decoded_mesh.get_attribute_by_name('joints')
335-
decoded_weights = decoded_mesh.get_attribute_by_name('weights')
346+
decoded_object = DracoPy.decode(binary)
347+
if object_type is DracoPy.DracoMesh:
348+
assert type(decoded_object) is DracoPy.DracoMesh
349+
else:
350+
assert type(decoded_object) is DracoPy.DracoPointCloud
351+
352+
assert len(decoded_object.attributes) == len(
353+
set([attr["unique_id"] for attr in decoded_object.attributes])
354+
)
355+
decoded_tangents = decoded_object.get_attribute_by_name('tangents')
356+
decoded_joints = decoded_object.get_attribute_by_name('joints')
357+
decoded_weights = decoded_object.get_attribute_by_name('weights')
336358

337359
assert decoded_tangents["attribute_type"] == DracoPy.AttributeType.GENERIC
338360
assert decoded_tangents["data_type"] == DracoPy.DataType.DT_FLOAT32

0 commit comments

Comments
 (0)