-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
base: main
Are you sure you want to change the base?
Issue 7153 - 3DGS IO #7191
Conversation
* 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>
Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes. |
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
There was a problem hiding this 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"); } | |||
|
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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" | |||
|
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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>(); | ||
} | ||
|
There was a problem hiding this comment.
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_); |
There was a problem hiding this comment.
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])); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
...)"
There was a problem hiding this 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:
- Start with a ply file as a string [Done].
- Create tensors for each of the attributes (manually fill them in). See example here.
- Write ply string to disk as ply.
- Read it back from disk.
- Test - all attributes are present, are the right shape and data type as the tensors in step 2.
- Test all attribute values match (See example here).
splat:
- 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. - Start with the same tensor attributes as in the ply test.
- Write it to disk as splat.
- Read it back from disk.
- Test - all attributes are present, are the right shape and data type as the tensors.
- 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); |
There was a problem hiding this comment.
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
...)"
There was a problem hiding this comment.
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.
Fixes #7153
Motivation and Context
Provide IO capabilities with 3DGS support for files like PLY and SPLAT.
Checklist:
python util/check_style.py --apply
to apply Open3D code styleto my code.
updated accordingly.
results (e.g. screenshots or numbers) here.
Notes