11 #include <tbb/parallel_sort.h>
13 #include <Eigen/Dense>
30 constexpr
double SH_C0 = 0.28209479177387814;
31 constexpr
int SPLAT_GAUSSIAN_BYTE_SIZE = 32;
34 inline double sigmoid(
double x) {
return 1.0 / (1.0 + std::exp(-x)); }
36 template <
typename scalar_t>
37 Eigen::Array<uint8_t, 4, 1> ComputeColor(
const scalar_t *f_dc_ptr,
38 const scalar_t *opacity_ptr) {
39 Eigen::Array<float, 4, 1>
color;
40 color[0] = 0.5 + SH_C0 * f_dc_ptr[0];
41 color[1] = 0.5 + SH_C0 * f_dc_ptr[1];
42 color[2] = 0.5 + SH_C0 * f_dc_ptr[2];
43 color[3] = sigmoid(*opacity_ptr);
45 return (
color * 255).round().cwiseMin(255.0).cwiseMax(0.0).cast<uint8_t>();
50 std::vector<int64_t> SortedSplatIndices(geometry::TensorMap &t_map) {
51 auto num_gaussians = t_map[
"opacity"].GetShape(0);
52 std::vector<int64_t> indices(num_gaussians);
53 std::iota(indices.begin(), indices.end(), 0);
56 const float *scale_data = t_map[
"scale"].GetDataPtr<
float>();
57 const float *opacity_data = t_map[
"opacity"].GetDataPtr<
float>();
58 const auto scle_grp_size = t_map[
"scale"].GetShape(1);
62 indices.begin(), indices.end(),
63 [&](
size_t left,
size_t right) ->
bool {
65 float scale_left = scale_data[left * scle_grp_size] +
66 scale_data[left * scle_grp_size + 1] +
67 scale_data[left * scle_grp_size + 2];
68 float scale_right = scale_data[right * scle_grp_size] +
69 scale_data[right * scle_grp_size + 1] +
70 scale_data[right * scle_grp_size + 2];
72 float score_left = -std::exp(scale_left) /
73 (1 + std::exp(-opacity_data[left]));
74 float score_right = -std::exp(scale_right) /
75 (1 + std::exp(-opacity_data[right]));
77 return score_left < score_right;
99 if (file_size == 0 || file_size % SPLAT_GAUSSIAN_BYTE_SIZE > 0) {
101 "Read SPLAT failed: file {} does not contain "
102 "a whole number of Gaussians. File Size {}"
103 " bytes, Gaussian Size {} bytes.",
104 filename, file_size, SPLAT_GAUSSIAN_BYTE_SIZE);
113 char buffer[SPLAT_GAUSSIAN_BYTE_SIZE];
114 const char *buffer_position = buffer;
115 const char *buffer_scale = buffer_position + 3 *
sizeof(float);
116 const uint8_t *buffer_color =
117 reinterpret_cast<const uint8_t *
>(buffer_scale) +
119 const uint8_t *buffer_rotation = buffer_color + 4 *
sizeof(uint8_t);
120 int number_of_points =
121 static_cast<int>(file_size / SPLAT_GAUSSIAN_BYTE_SIZE);
143 float *position_ptr =
145 float *scale_ptr = pointcloud.
GetPointAttr(
"scale").GetDataPtr<
float>();
146 float *f_dc_ptr = pointcloud.
GetPointAttr(
"f_dc").GetDataPtr<
float>();
149 float *rot_ptr = pointcloud.
GetPointAttr(
"rot").GetDataPtr<
float>();
152 for (
size_t index = 0; file.
ReadData(buffer, SPLAT_GAUSSIAN_BYTE_SIZE);
155 std::memcpy(position_ptr + index * 3, buffer_position,
157 std::memcpy(scale_ptr + index * 3, buffer_scale, 3 *
sizeof(
float));
160 float *f_dc = f_dc_ptr + index * 3;
161 for (
int i = 0; i < 3; i++) {
162 f_dc[i] = ((buffer_color[i] / 255.0) - 0.5) / SH_C0;
165 float *opacity = opacity_ptr + index;
166 if (buffer_color[3] == 0) {
168 }
else if (buffer_color[3] == 255) {
169 opacity[0] = -std::numeric_limits<float>::lowest();
171 opacity[0] = -log(1 / (buffer_color[3] / 255.0) - 1);
175 float *rot_float = rot_ptr + index * 4;
177 for (
int i = 0; i < 4; i++) {
178 rot_float[i] = (buffer_rotation[i] / 128.0) - 1.0;
179 quat_norm += rot_float[i] * rot_float[i];
181 quat_norm = sqrt(quat_norm);
182 if (quat_norm > std::numeric_limits<float>::epsilon()) {
183 for (
int i = 0; i < 4; i++) {
184 rot_float[i] /= quat_norm;
193 if (index % 1000 == 0) {
201 }
catch (
const std::exception &e) {
220 "Write SPLAT failed: point cloud is not a Gaussian Splat.");
228 for (
auto attr : {
"positions",
"scale",
"rot",
"f_dc",
"opacity"}) {
229 t_map[attr] = t_map[attr]
234 float *positions_ptr = t_map[
"positions"].GetDataPtr<
float>();
235 float *scale_ptr = t_map[
"scale"].GetDataPtr<
float>();
236 float *f_dc_ptr = t_map[
"f_dc"].GetDataPtr<
float>();
237 float *opacity_ptr = t_map[
"opacity"].GetDataPtr<
float>();
238 float *rot_ptr = t_map[
"rot"].GetDataPtr<
float>();
239 constexpr
int N_POSITIONS = 3;
240 constexpr
int N_SCALE = 3;
241 constexpr
int N_F_DC = 3;
242 constexpr
int N_OPACITY = 1;
243 constexpr
int N_ROT = 4;
250 auto splat_file = std::ofstream(
filename, std::ios::binary);
252 splat_file.exceptions(std::ofstream::badbit);
254 }
catch (
const std::ios_base::failure &) {
264 std::vector<int64_t> sorted_indices = SortedSplatIndices(t_map);
267 for (int64_t i = 0; i < num_gaussians; i++) {
268 int64_t g_idx = sorted_indices[i];
271 splat_file.write(
reinterpret_cast<const char *
>(
272 positions_ptr + N_POSITIONS * g_idx),
273 N_POSITIONS *
sizeof(
float));
277 reinterpret_cast<const char *
>(scale_ptr + N_SCALE * g_idx),
278 N_SCALE *
sizeof(
float));
281 auto color = ComputeColor(f_dc_ptr + N_F_DC * g_idx,
282 opacity_ptr + N_OPACITY * g_idx);
283 splat_file.write(
reinterpret_cast<const char *
>(
color.data()),
284 4 *
sizeof(uint8_t));
287 int rot_offset = N_ROT * g_idx;
288 Eigen::Vector4f rot{rot_ptr[rot_offset], rot_ptr[rot_offset + 1],
289 rot_ptr[rot_offset + 2],
290 rot_ptr[rot_offset + 3]};
291 if (
auto quat_norm = rot.norm();
292 quat_norm > std::numeric_limits<float>::epsilon()) {
295 rot = {1.f, 0.f, 0.f, 0.f};
299 rot = (rot * 128.0).array().round() + 128.0;
301 rot.cwiseMin(255.0).cwiseMax(0.0).cast<uint8_t>().eval();
302 splat_file.write(
reinterpret_cast<const char *
>(uint8_rot.data()),
303 4 *
sizeof(uint8_t));
312 }
catch (
const std::ios_base::failure &e) {
cmdLineReadable * params[]
int64_t GetLength() const
static Tensor Empty(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor with uninitialized values.
A point cloud contains a list of 3D points.
bool IsGaussianSplat() const
void SetPointPositions(const core::Tensor &value)
Set the value of the "positions" attribute. Convenience function.
core::Tensor & GetPointPositions()
Get the value of the "positions" attribute. Convenience function.
bool IsEmpty() const override
Returns !HasPointPositions().
const TensorMap & GetPointAttr() const
Getter for point_attr_ TensorMap. Used in Pybind.
PointCloud & Clear() override
Clear all data in the point cloud.
void SetPointAttr(const std::string &key, const core::Tensor &value)
TensorMap Contiguous() const
void SetTotal(int64_t total)
bool Update(int64_t count)
bool Open(const std::string &filename, const std::string &mode)
Open a file.
int64_t CurPos()
Returns current position in the file (ftell).
int64_t GetFileSize()
Returns the file size in bytes.
size_t ReadData(T *data, size_t num_elems)
bool ReadPointCloudFromSPLAT(const std::string &filename, geometry::PointCloud &pointcloud, const cloudViewer::io::ReadPointCloudOption ¶ms)
bool WritePointCloudToSPLAT(const std::string &filename, const geometry::PointCloud &pointcloud, const cloudViewer::io::WritePointCloudOption ¶ms)
Generic file read and write utility for python interface.
Optional parameters to ReadPointCloud.
Optional parameters to WritePointCloud.