diff --git a/README.md b/README.md index 8b282ca..03395a5 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,6 @@ Generally speaking, hapPLY uses C++ exceptions to communicate errors-- most of t ## Known issues: - Writing floating-point values of `inf` or `nan` in ASCII mode is not supported, because the .ply format does not specify how they should be written (C++'s ofstream and ifstream don't even treat them consistently). These values work just fine in binary mode. -- Currently hapPLY does not allow the user to specify a type for the variable which indicates how many elements are in a list; it always uses `uchar` (and throws and error if the data does not fit in a uchar). Note that at least for mesh-like data, popular software only accepts `uchar`. - Almost all modern computers are little-endian. If you happen to have a big-endian platform, be aware that the codebase has not been tested in a big-endian environment, and might have bugs related to binary reading/writing there. Note that the _platform_ endianness is distinct from the _file_ endianness---reading/writing either big- or little-endian files certainly works just fine as long as you're running the code on a little-endian computer (as you problably are). diff --git a/happly.h b/happly.h index 4ee8c71..ff99bf6 100644 --- a/happly.h +++ b/happly.h @@ -439,14 +439,28 @@ class TypedListProperty : public Property { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } + size_t maxCount = 0; // Populate list with data flattenedIndexStart.push_back(0); for (const std::vector& vec : data_) { for (const T& val : vec) { flattenedData.emplace_back(val); } + if(vec.size() > maxCount) + maxCount = vec.size(); flattenedIndexStart.push_back(flattenedData.size()); } + + if(maxCount > std::numeric_limits::max()) { + throw std::runtime_error( + "List property has an element with more entries than fit in a uint32."); + } else if(maxCount > std::numeric_limits::max()) { + listCountBytes = 4; + } else if(maxCount > std::numeric_limits::max()) { + listCountBytes = 2; + } else { + listCountBytes = 1; + } }; virtual ~TypedListProperty() override{}; @@ -547,8 +561,14 @@ class TypedListProperty : public Property { * @param outStream Stream to write to. */ virtual void writeHeader(std::ostream& outStream) override { - // NOTE: We ALWAYS use uchar as the list count output type - outStream << "property list uchar " << typeName() << " " << name << "\n"; + if(listCountBytes == 1) { + outStream << "property list uchar " << typeName() << " " << name << "\n"; + } else if(listCountBytes == 2) { + outStream << "property list ushort " << typeName() << " " << name << "\n"; + } else if(listCountBytes == 4) { + outStream << "property list uint " << typeName() << " " << name << "\n"; + } else + throw std::runtime_error("Unknown value of listCountBytes."); } /** @@ -561,13 +581,7 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - outStream << dataCount; outStream.precision(std::numeric_limits::max_digits10); for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) { @@ -585,16 +599,20 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - uint8_t count = static_cast(dataCount); + if(listCountBytes == 1) { + uint8_t count = static_cast(dataCount); + outStream.write((char*)&count, listCountBytes); + } else if(listCountBytes == 2) { + uint16_t count = static_cast(dataCount); + outStream.write((char*)&count, listCountBytes); + } else if(listCountBytes == 4) { + uint32_t count = static_cast(dataCount); + outStream.write((char*)&count, listCountBytes); + } else + throw std::runtime_error("Unknown value of listCountBytes."); - outStream.write((char*)&count, sizeof(uint8_t)); - outStream.write((char*)&flattenedData[dataStart], count * sizeof(T)); + outStream.write((char*)&flattenedData[dataStart], dataCount * sizeof(T)); } /** @@ -607,15 +625,19 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - uint8_t count = static_cast(dataCount); + if(listCountBytes == 1) { + uint8_t count = static_cast(dataCount); + outStream.write((char*)&count, listCountBytes); + } else if(listCountBytes == 2) { + uint16_t count = swapEndian(static_cast(dataCount)); + outStream.write((char*)&count, listCountBytes); + } else if(listCountBytes == 4) { + uint32_t count = swapEndian(static_cast(dataCount)); + outStream.write((char*)&count, listCountBytes); + } else + throw std::runtime_error("Unknown value of listCountBytes."); - outStream.write((char*)&count, sizeof(uint8_t)); for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) { T value = swapEndian(flattenedData[iFlat]); outStream.write((char*)&value, sizeof(T)); diff --git a/test/main_test.cpp b/test/main_test.cpp index 287adc2..84c6559 100644 --- a/test/main_test.cpp +++ b/test/main_test.cpp @@ -961,6 +961,338 @@ TEST(TypedListReadWriteTest, ReadWriteDoubleBinarySwap) { EXPECT_EQ(testData, testDataBinary); } +// Tests for list size +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} + // === Test error and utility behavior // Errors get thrown