• Stars
    star
    278
  • Rank 142,871 (Top 3 %)
  • Language
    C++
  • License
    MIT License
  • Created about 6 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A C++ header-only parser for the PLY file format. Parse .ply happily!

hapPLY

A header-only C++ reader/writer for the PLY file format. Parse .ply happily!

Features:

  • Header only-- drop in and use!
  • Read and write to plaintext and binary variants of format with same API!
  • Supports general data in .ply files, along with common-case helpers for reading/writing mesh data!
  • Automatic type promotion-- eg, if a file contains a float field, you can seamlessly read it as a double!
  • Tested, documented, and MIT-licensed!

The .ply format and hapPLY

The .ply format is a general-purpose flat file format useful for recording numerical data on unstructured domains, which includes both plaintext and binary representations. The format has been kicking around since the 90s: Paul Bourke's webpage serves as both an introduction and the most official specification. hapPLY grew out of my own personal code for .ply files-- the format is extremely useful for working with 3D meshes and other geometric data, but no easily accessible C++ implementation was available.

Although the .ply format is commonly used to store 3D mesh and point cloud data, the format itself technically has nothing to do with meshes or point clouds; it simply specifies a collection elements, and data (called properties) associated with those elements. For instance in a mesh, the elements are vertices and faces; vertices then have properties like "position" and "color", while faces have a property which is a list of vertex indices. hapPLY exposes a general API for reading and writing elements and properties, as well as special-purpose helpers for the common conventions surrounding mesh data.

Examples

Read basic data

#include "happly.h"

// Construct a data object by reading from file
happly::PLYData plyIn("my_file.ply");

// Get data from the object
std::vector<float> elementA_prop1 = plyIn.getElement("elementA").getProperty<float>("prop1");
std::vector<int> elementA_prop2 = plyIn.getElement("elementA").getProperty<double>("prop1");
std::vector<std::vector<double>> elementB_listProp = 
    plyIn.getElement("elementB").getListProperty<double>("listprop1");

// Type promotion is automatic for numeric types: even if this property was stored as a float, 
// we can access it as a double
std::vector<double> elementA_prop1_as_double = 
    plyIn.getElement("elementA").getProperty<double>("prop1"); 

Write basic data

#include "happly.h"

// Suppose these hold your data
std::vector<float> elementA_prop1;
std::vector<int> elementA_prop2;
std::vector<std::vector<double>> elementB_listProp;

// Create an empty object
happly::PLYData plyOut;

// Add elements
plyOut.addElement("elementA", 20);
plyOut.addElement("elementB", 42);

// Add properties to those elements
plyOut.getElement("elementA").addProperty<float>("prop1", elementA_prop1);
plyOut.getElement("elementA").addProperty<int>("prop2", elementA_prop2);
plyOut.getElement("elementB").addListProperty<double>("listprop1", elementB_listProp);

// Write the object to file
plyOut.write("my_output_file.ply", happly::DataFormat::Binary);

Read mesh-like data

#include "happly.h"

// Construct the data object by reading from file
happly::PLYData plyIn("my_mesh_file.ply");

// Get mesh-style data from the object
std::vector<std::array<double, 3>> vPos = plyIn.getVertexPositions();
std::vector<std::vector<size_t>> fInd = plyIn.getFaceIndices<size_t>();

Write mesh-like data

#include "happly.h"

// Suppose these hold your data
std::vector<std::array<double, 3>> meshVertexPositions;
std::vector<std::array<double, 3>> meshVertexColors;
std::vector<std::vector<size_t>> meshFaceIndices;

// Create an empty object
happly::PLYData plyOut;

// Add mesh data (elements are created automatically)
plyOut.addVertexPositions(meshVertexPositions);
plyOut.addVertexColors(meshVertexColors);
plyOut.addFaceIndices(meshFaceIndices);


// Write the object to file
plyOut.write("my_output_mesh_file.ply", happly::DataFormat::ASCII);

API

This assumes a basic familiarity with the file format; I suggest reading Paul Bourke's webpage if you are new to .ply.

All of the outward-facing functionality of hapPLY is grouped under a single (namespaced) class called happly::PLYData, which represents a collection of elements and their properties. PLYData objects can be constructed from an existing file PLYData::PLYData("my_input.ply"), or you can fill with your own data and then write to file PLYData::write("my_output.ply", DataFormat::ASCII).

Generally speaking, hapPLY uses C++ exceptions to communicate errors-- most of these methods will throw if something is wrong. hapPLY attempts to provide basic sanity checks and informative errors, but does not guarantee robustness to malformed input.

Reading and writing objects:

  • PLYData() Construct an empty PLYData containing no elements or properties.

  • PLYData(std::string filename, bool verbose = false) Construct a new PLYData object from a file, automatically detecting whether the file is plaintext or binary. If verbose=true, useful information about the file will be printed to stdout.

  • PLYData(std::istream& inStream, bool verbose = false) Like the previous constructor, but reads from anistream.

  • PLYData::validate() Perform some basic sanity checks on the object, throwing if any fail. Called internally before writing.

  • PLYData::write(std::string filename, DataFormat format = DataFormat::ASCII) Write the object to file. Specifying DataFormat::ASCII, DataFormat::Binary, or DataFormat::BinaryBigEndian controls the kind of output file.

  • PLYData::write(std::ostream& outStream, DataFormat format = DataFormat::ASCII) Like the previous method, but writes to anostream.

Accessing and adding data to an object:

  • void addElement(std::string name, size_t count) Add a new element type to the object, with the given name and number of elements.

  • Element& getElement(std::string target) Get a reference to an element type contained in the object.

  • bool hasElement(std::string target) Check if an element type is contained in the object.

  • std::vector<std::string> getElementNames() List of all element names.

  • std::vector<T> Element::getProperty(std::string propertyName) Get a vector of property data for an element. Will automatically promote types if possible, eg getProperty<int>("my_prop") will succeed even if the object contains "my_prop" with type short.

  • std::vector<std::vector<T>> Element::getListProperty(std::string propertyName) Get a vector of list property data for an element. Supports type promotion just like getProperty().

  • void Element::addProperty(std::string propertyName, std::vector<T>& data) Add a new property to an element type. data must be the same length as the number of elements of that type.

  • void addListProperty(std::string propertyName, std::vector<std::vector<T>>& data) Add a new list property to an element type. data must be the same length as the number of elements of that type.

Misc object options:

  • std::vector<std::string> PLYData::comments Comments included in the .ply file, one string per line. These are populated after reading and written when writing.

  • std::vector<std::string> PLYData::objInfoComments Lines prefaced with obj_info included in the .ply file, which are effectively a different kind of comment, one string per line. These seem to be an ad-hoc extension to .ply, but they are pretty common, so we support them.

Common-case helpers for mesh data:

  • std::vector<std::array<double, 3>> getVertexPositions(std::string vertexElementName = "vertex") Returns x,y,z vertex positions from an object. vertexElementName specifies the name of the element type holding vertices, which is conventionally "vertex".

  • void addVertexPositions(std::vector<std::array<double, 3>>& vertexPositions) Adds x,y,z vertex positions to an object, under the element name "vertex".

  • std::vector<std::array<unsigned char, 3>> getVertexColors(std::string vertexElementName = "vertex") Returns r,g,b vertex colors from an object. vertexElementName specifies the name of the element type holding vertices, which is conventionally "vertex".

  • void addVertexColors(std::vector<std::array<unsigned char, 3>>& vertexColors) Adds r,g,b vertex colors positions to an object, under the element name "vertex".

  • void addVertexColors(std::vector<std::array<double, 3>>& vertexColors) Adds r,g,b vertex colors positions to an object, under the element name "vertex". Assumes input is in [0.0,1.0], and internally converts to 0-255 char values

  • std::vector<std::vector<T>> getFaceIndices() Returns indices in to a vertex list for each face. Usually 0-indexed, but there are no formal rules in the format. Supports type promotion as in getProperty(), and furthermore converts signed to unsigned and vice-versa, though the conversion is performed naively.

  • void addFaceIndices(std::vector<std::vector<T>>& indices) Adds vertex indices for faces to an object, under the element name "face" with the property name "vertex_indices". Automatically converts to a 32-bit integer type with the same signedness as the input type, and throws if the data cannot be converted to that type.

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).

Current TODOs:

  • Add more common-case helpers for meshes (texture coordinates, etc)
  • Add common-case helpers for point clouds
  • Bindings for Python, Matlab?

By Nicholas Sharp. Credit to Keenan Crane for early feedback and the logo!

Development of this software was funded in part by NSF Award 1717320, an NSF graduate research fellowship, and gifts from Adobe Research and Autodesk, Inc.

More Repositories

1

polyscope

A C++ & Python viewer for 3D data like meshes and point clouds
C++
1,616
star
2

geometry-central

Applied 3D geometry in C++, with a focus on surface meshes.
C++
985
star
3

potpourri3d

An invigorating blend of 3D geometry tools in Python.
Python
388
star
4

diffusion-net

Pytorch implementation of DiffusionNet for fast and robust learning on 3D surfaces like meshes or point clouds.
Python
369
star
5

robust-laplacians-py

Build high-quality Laplace matrices on meshes and point clouds in Python. Implements [Sharp & Crane SGP 2020].
C++
178
star
6

neural-implicit-queries

Queries on neural implicit surfaces via range analysis: ray casting, intersection, closest point, & more. SIGGRAPH 2022 paper. JAX implementation.
Python
169
star
7

neural-physics-subspaces

Fit low-dimensional subspaces to physical systems with neural networks (SIGGRAPH 2023)
Python
134
star
8

DDGSpring2016

Code repository for 15-869 Discrete Differential Geometry at CMU in Spring 2016.
Python
121
star
9

intrinsic-triangulations-tutorial

An introductory course intrinsic triangulations for powerful & robust geometry processing --- tutorial code and links.
Python
119
star
10

nonmanifold-laplacian

A robust Laplace matrix for general (possibly nonmanifold) triangle meshes, and point clouds [Sharp & Crane SGP 2020]
C++
113
star
11

learned-triangulation

Source code for "PointTriNet: Learned Triangulation of 3D Point Sets", by Nicholas Sharp and Maks Ovsjanikov at ECCV 2020
Python
98
star
12

variational-surface-cutting

Codebase for "Variational Surface Cutting" by Sharp & Crane, SIGGRAPH 2018
C++
87
star
13

flip-geodesics-demo

Construct geodesic paths, loops, networks on surface with a fast and simple edge flipping algorithm. C++ demo app and more.
C++
80
star
14

vector-heat-demo

C++ demo of the Vector Heat Method (Sharp, Soliman, and Crane. 2019.)
C++
58
star
15

navigating-intrinsic-triangulations-demo

Demo code for "Navigating Intrinsic Triangulations". Sharp, Soliman, and Crane. 2019
C++
46
star
16

gc-polyscope-project-template

A template project to get started with geometry-central and Polyscope.
C++
43
star
17

polyscope-py

Python bindings for Polyscope
Python
29
star
18

arrgh

A small python utility to pretty-print a table summarizing arrays & scalars from numpy, pytorch, etc.
Python
24
star
19

discretization-robust-correspondence-benchmark

Benchmark for the generalization of 3D machine learning models across different remeshing/samplings of a surface.
Python
11
star
20

geometry-central-tutorials

Tutorials for the geometry-central geometry processing library.
C++
9
star
21

libigl-polyscope-project-template

An example project and build system using libIGL and Polyscope
CMake
7
star
22

RNA-Surface-Segmentation-Dataset

A dataset of segmented RNA molecule surfaces, as a benchmark task in 3D machine learning on surfaces. From Poulenard et al., 3DV 2019.
7
star
23

polyscope-docs

Documentation for polyscope
HTML
3
star
24

nmwsharp.github.io

HTML
1
star