Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 7153 - 3DGS IO #7191

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

lumurillo
Copy link
Contributor

Fixes #7153

  • Bug fix (non-breaking change which fixes an issue): Fixes #
  • New feature (non-breaking change which adds functionality). Resolves #
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) Resolves #

Motivation and Context

Provide IO capabilities with 3DGS support for files like PLY and SPLAT.

Checklist:

  • I have run python util/check_style.py --apply to apply Open3D code style
    to my code.
  • This PR changes Open3D behavior or adds new functionality.
    • Both C++ (Doxygen) and Python (Sphinx / Google style) documentation is
      updated accordingly.
    • I have added or updated C++ and / or Python unit tests OR included test
      results
      (e.g. screenshots or numbers) here.
  • I will follow up and update the code if CI fails.
  • For fork PRs, I have selected Allow edits from maintainers.

Notes

  • Some of the test may fail because we need that the sample files are uploaded to the corresponding repo.

lumurillo and others added 3 commits March 7, 2025 12:45
* Include additional 3DGS properties for reading operation

* Add validation methods for 3DGS mandatory properties

* Read and Write including additional vertex

* Add the validation check to the write method

* Add a unit test for a valid ReadCloutPoint operation

* PLY read to point cloud

* PLY write correction

* Inline documentation

* Add constant defines for magic numbers and attribute names

* Add unit tests for reading and writing 3DGS formatted point clouds

* Add 3DGS attribute verification to the read operation

---------

Co-authored-by: Juan Cruz <juan.cruz.naranjo@intel.com>
Co-authored-by: Gomez Rodriguez, Diego <diego.gomez.rodriguez@intel.com>
* Add splat writer

* Add splat reader

* Splat reader added to PointCloudIO.h

* Fix CMakeList.txt

* Add test for reading PointCloud from SPLAT format

* Fixing Style

---------

Co-authored-by: ikun03 <ikunal03@gmail.com>
Copy link

update-docs bot commented Mar 8, 2025

Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes.

Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@lumurillo lumurillo changed the title Issue 7153 dev Issue 7153 - 3DGS IO Mar 8, 2025
Copy link
Member

@ssheorey ssheorey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @lumurillo and team for this very useful contribution!

Naming: Can we replace the use of 3DGS to GaussianSplat everywhere?
This also take care of variants of 3DGS such as 2D Gaussian Splatting. Also splat seems to be the more common / short name for this representation in other software.

Btw: a good way to remember the f_rest size is that it's n^2-1 for order n :-)

PS: Review is WIP: Will review test and docs later.

@@ -214,6 +214,41 @@ class PointCloud : public Geometry, public DrawableGeometry {
/// This is a convenience function.
bool HasPointNormals() const { return HasPointAttr("normals"); }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably skip these functions - I don't think they will be used much.
Instead, can I recommend:

int GaussianSplatGetSHOrder() const; 
// Get order of spherical harmonic data present.
// f_rest is absent -> return 0;
// return 1, 2, 3 if f_rest.shape[1] is (3, 8, 15) respectively. Throw error for invalid shape.
bool GaussianSplatReduceSHOrder(int order=0);
// Resize f_rest to the given order to compress it.

// Other attribute.
return std::make_tuple(name, 1, 0);
}

bool Is3DGSPointCloud(const geometry::PointCloud &pointcloud) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to the PointCloud class, since its more generally useful other than IO? Also rename to PointCloud::IsGaussianSplat().
Also as part of this, we should check shape (as you do below), i.e. an invalid Splat is not a Splat (just another point cloud).

@@ -18,6 +18,29 @@
#include "open3d/utility/Logging.h"
#include "open3d/utility/ProgressReporters.h"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can simplify this - the attribute names are fixed, and are used in other parts of Open3D as well. We can directly use the strings for the property names instead of defining macros.

#define SCALE_SUFFIX_INDEX 6
#define F_DC_SUFFIX_INDEX 5
#define F_REST_SUFFIX_INDEX 7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest C++ guidelines prefer constexpr int to preprocessor macros. In fact with C++26 you mostly don't need the preprocessor at all!
With constexpr int, you will need to put them in an anonymous namespace to ensure they don't leak outside this translation unit.

#define SCALE_SUFFIX_INDEX 6
#define F_DC_SUFFIX_INDEX 5
#define F_REST_SUFFIX_INDEX 7

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest C++ guidelines prefer constexpr int to preprocessor macros. In fact, with C++26 you mostly don't need the preprocessor at all!
With constexpr int, you will need to put them in an anonymous namespace to ensure they don't leak outside this translation unit.
PS: Perhaps POSITIONS_ATTR_DIM (etc.) is more appropriate, since we cannot have less than 3 dims for position (or RGB, etc.) in Open3D. 2D is not supported.

// Convert color to uint8 (scale, clip, and cast)
return (color * 255.0).cwiseMin(255.0).cwiseMax(0.0).cast<int>();
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use C++ fstreams with exceptions for error handling for faster IO.

// Positions
DISPATCH_DTYPE_TO_TEMPLATE(positions_attr.dtype_, [&]() {
const scalar_t *positions_ptr =
static_cast<const scalar_t *>(positions_attr.data_ptr_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Alternative:

  • data_ptr: tmap["positions"].GetDataPtr<scalar_t>()
  • group_size: tmap["positions"].GetShape(1)

rot.cwiseMin(255.0).cwiseMax(0.0).cast<int>();

for (int idx = 0; idx < 4; ++idx) {
splat_write_byte(splat_file, int_to_byte(int_rot[idx]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bounds checking inside int_to_byte is unnecessary, since we have clamped the values above. Simpler static_cast should be faster.

@@ -309,16 +359,11 @@ TEST(TPointCloudIO, WritePTSColorConversion1) {

// Check PTS color boolean to uint8 conversion.
TEST(TPointCloudIO, WritePTSColorConversion2) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this .pts file IO test deletion accidental?

std::string filename_out = utility::filesystem::GetTempDirectoryPath() +
"/test_sample_right_3dgs_format.ply";
std::ofstream outfile;
outfile.open(filename_out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make this a global variable (anon namespace) since this is used in multiple places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can use raw string literals to avoid typing the \n:

R"(ply
format ascii 1.0
...)"

Copy link
Member

@ssheorey ssheorey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to modify the unit tests a bit:

ply:

  1. Start with a ply file as a string [Done].
  2. Create tensors for each of the attributes (manually fill them in). See example here.
  3. Write ply string to disk as ply.
  4. Read it back from disk.
  5. Test - all attributes are present, are the right shape and data type as the tensors in step 2.
  6. Test all attribute values match (See example here).

splat:

  1. Start with .splat file as a binary blob - you can get this by converting the .ply file above to .splat with the reference convert.py script (antimatter github). You can convert it to a C array with xxd -i infile.splat outfile.h. If the C array is too big, let me know and we can add it directly to the git repo (with some cmake additions). This step directly proves that antimatter reference splats can be read by Open3D.
  2. Start with the same tensor attributes as in the ply test.
  3. Write it to disk as splat.
  4. Read it back from disk.
  5. Test - all attributes are present, are the right shape and data type as the tensors.
  6. Test all attribute values match (See example here).

This simplifies the tests by using round trip test, no data file is needed for splat,

std::string filename_out = utility::filesystem::GetTempDirectoryPath() +
"/test_sample_right_3dgs_format.ply";
std::ofstream outfile;
outfile.open(filename_out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can use raw string literals to avoid typing the \n:

R"(ply
format ascii 1.0
...)"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't add data to the repo (as much as possible), so we can delete it from here. Instead, I'll add it to the Open3D downloads site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3D Gaussian splatting IO
3 participants