Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ namespace openstrata::gs {
// and makes the missing code obvious in a bug report.
inline constexpr const char* kUnspecifiedDiagnosticCode = "GS-E000";

inline std::string FormatDiagnostic(const char* code, const std::string& message)
inline std::string FormatDiagnostic(const char* code,
const std::string& message)
{
if (!code) {
code = kUnspecifiedDiagnosticCode;
Expand Down
24 changes: 9 additions & 15 deletions libs/gaussian-core/include/openstrata/gs/GaussianMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ bool DecodeLogScale(const Float3& stored, Float3* actual) noexcept;

// Returns false only for non-finite input. A near-zero quaternion is replaced
// with identity and reported through replacedWithIdentity.
bool NormalizeQuaternion(
const Quaternion& stored,
Quaternion* normalized,
bool* replacedWithIdentity = nullptr,
bool* changed = nullptr) noexcept;
bool NormalizeQuaternion(const Quaternion& stored, Quaternion* normalized,
bool* replacedWithIdentity = nullptr,
bool* changed = nullptr) noexcept;

// coefficientCount includes the DC term. Valid layouts are 1, 4, 9, 16, ...
bool InferShDegree(std::size_t coefficientCount, int* degree) noexcept;
Expand All @@ -38,15 +36,11 @@ void FlipYZAxes(GaussianCloudData* cloud) noexcept;
// kit compares against, so a stage need not be authored to know the extent a
// cloud will produce. Returns false when count is zero or a bound leaves
// float range; the outputs are untouched on failure.
bool ComputeCloudExtent(
const Float3* positions,
const Float3* scales,
std::size_t count,
Float3* outMinimum,
Float3* outMaximum) noexcept;

bool ValidateGaussianCloud(
const GaussianCloudData& cloud,
std::string* error = nullptr) noexcept;
bool ComputeCloudExtent(const Float3* positions, const Float3* scales,
std::size_t count, Float3* outMinimum,
Float3* outMaximum) noexcept;

bool ValidateGaussianCloud(const GaussianCloudData& cloud,
std::string* error = nullptr) noexcept;

} // namespace openstrata::gs
14 changes: 6 additions & 8 deletions libs/gaussian-core/include/openstrata/gs/GaussianSizeMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
namespace openstrata::gs {

// True and writes `a * b` unless the product overflows std::size_t.
inline bool CheckedMulSize(
std::size_t a, std::size_t b, std::size_t* product) noexcept
inline bool CheckedMulSize(std::size_t a, std::size_t b,
std::size_t* product) noexcept
{
if (!product) {
return false;
Expand All @@ -40,8 +40,8 @@ inline bool CheckedMulSize(
}

// True and writes `a + b` unless the sum overflows std::size_t.
inline bool CheckedAddSize(
std::size_t a, std::size_t b, std::size_t* sum) noexcept
inline bool CheckedAddSize(std::size_t a, std::size_t b,
std::size_t* sum) noexcept
{
if (!sum) {
return false;
Expand All @@ -57,10 +57,8 @@ inline bool CheckedAddSize(
// (GAUSSIAN_MODEL_CONTRACT.md §3), overflow-checked. Fails on a degree
// outside the supported 0..kMaxShDegree range rather than computing a length
// the shared gate would reject anyway.
inline bool ComputeRestCoefficientCount(
std::size_t gaussianCount,
int shDegree,
std::size_t* restCount) noexcept
inline bool ComputeRestCoefficientCount(std::size_t gaussianCount, int shDegree,
std::size_t* restCount) noexcept
{
if (!restCount || shDegree < 0 || shDegree > kMaxShDegree) {
return false;
Expand Down
20 changes: 11 additions & 9 deletions libs/gaussian-core/include/openstrata/gs/testing/CloudContract.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ namespace openstrata::gs::testing {
// Returns one message per violated rule; an empty result means the cloud
// conforms. Returning messages rather than asserting keeps the checker
// independent of any bundle's test harness.
inline std::vector<std::string> CheckCloudContract(const GaussianCloudData& cloud)
inline std::vector<std::string>
CheckCloudContract(const GaussianCloudData& cloud)
{
std::vector<std::string> violations;
const auto fail = [&violations](std::string message) {
Expand All @@ -59,7 +60,8 @@ inline std::vector<std::string> CheckCloudContract(const GaussianCloudData& clou
const auto checkLength = [&](const char* name, std::size_t actual) {
if (actual != count) {
fail(std::string("§3: ") + name + " has length " +
std::to_string(actual) + ", expected " + std::to_string(count));
std::to_string(actual) + ", expected " +
std::to_string(count));
}
};
checkLength("positions", cloud.positions.size());
Expand All @@ -78,8 +80,8 @@ inline std::vector<std::string> CheckCloudContract(const GaussianCloudData& clou
const std::size_t expectedRest = count * (perGaussian - 1);
if (cloud.restCoefficients.size() != expectedRest) {
fail("§3: restCoefficients has length " +
std::to_string(cloud.restCoefficients.size()) + ", expected " +
std::to_string(expectedRest));
std::to_string(cloud.restCoefficients.size()) + ", expected " +
std::to_string(expectedRest));
}

const auto finite3 = [](const Float3& v) {
Expand All @@ -100,20 +102,20 @@ inline std::vector<std::string> CheckCloudContract(const GaussianCloudData& clou
fail("§3: non-finite scale" + at);
} else if (scale.x <= 0.0f || scale.y <= 0.0f || scale.z <= 0.0f) {
fail("§3: scale is not strictly positive" + at +
" (log-encoded scales reaching the model?)");
" (log-encoded scales reaching the model?)");
}

// §3: opacity is already through sigmoid, never a logit.
const float opacity = cloud.opacities[i];
if (!std::isfinite(opacity) || opacity < 0.0f || opacity > 1.0f) {
fail("§3: opacity outside [0, 1]" + at +
" (a logit reaching the model?)");
" (a logit reaching the model?)");
}

// §3: quaternions reach the model normalized.
const Quaternion& q = cloud.rotations[i];
const float norm = std::sqrt(
q.real * q.real + q.i * q.i + q.j * q.j + q.k * q.k);
const float norm =
std::sqrt(q.real * q.real + q.i * q.i + q.j * q.j + q.k * q.k);
if (!std::isfinite(norm) || std::fabs(norm - 1.0f) > 1.0e-4f) {
fail("§3: quaternion is not normalized" + at);
}
Expand All @@ -126,7 +128,7 @@ inline std::vector<std::string> CheckCloudContract(const GaussianCloudData& clou
for (std::size_t i = 0; i < cloud.restCoefficients.size(); ++i) {
if (!finite3(cloud.restCoefficients[i])) {
fail("§3: non-finite rest coefficient at index " +
std::to_string(i));
std::to_string(i));
}
}

Expand Down
102 changes: 50 additions & 52 deletions libs/gaussian-core/include/openstrata/gs/testing/DecoderTestKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ struct CloudTolerances {
// q and -q denote the same rotation and both are admissible
// (GAUSSIAN_MODEL_CONTRACT.md §3), so equality holds under whichever sign
// matches better.
inline bool QuaternionsEquivalent(
const Quaternion& a, const Quaternion& b, float tolerance) noexcept
inline bool QuaternionsEquivalent(const Quaternion& a, const Quaternion& b,
float tolerance) noexcept
{
const auto close = [tolerance](const Quaternion& p, const Quaternion& q) {
return std::fabs(p.real - q.real) <= tolerance &&
std::fabs(p.i - q.i) <= tolerance &&
std::fabs(p.j - q.j) <= tolerance &&
std::fabs(p.k - q.k) <= tolerance;
std::fabs(p.i - q.i) <= tolerance &&
std::fabs(p.j - q.j) <= tolerance &&
std::fabs(p.k - q.k) <= tolerance;
};
const Quaternion negated = {-b.real, -b.i, -b.j, -b.k};
return close(a, b) || close(a, negated);
Expand All @@ -121,10 +121,10 @@ inline bool QuaternionsEquivalent(
// within the tolerances. Compares structure (count, degree, lengths), every
// field per Gaussian, every rest coefficient by (gaussian, coefficient)
// index, and the deterministic derived extent -- without any USD stage.
inline std::vector<std::string> CompareClouds(
const GaussianCloudData& actual,
const GaussianCloudData& expected,
const CloudTolerances& tolerances = {})
inline std::vector<std::string>
CompareClouds(const GaussianCloudData& actual,
const GaussianCloudData& expected,
const CloudTolerances& tolerances = {})
{
std::vector<std::string> mismatches;
const auto fail = [&mismatches](std::string message) {
Expand All @@ -133,79 +133,78 @@ inline std::vector<std::string> CompareClouds(

if (actual.gaussianCount != expected.gaussianCount) {
fail("gaussianCount " + std::to_string(actual.gaussianCount) +
" != expected " + std::to_string(expected.gaussianCount));
" != expected " + std::to_string(expected.gaussianCount));
return mismatches;
}
if (actual.shDegree != expected.shDegree) {
fail("shDegree " + std::to_string(actual.shDegree) +
" != expected " + std::to_string(expected.shDegree));
fail("shDegree " + std::to_string(actual.shDegree) + " != expected " +
std::to_string(expected.shDegree));
return mismatches;
}
const auto checkLength = [&](
const char* name, std::size_t got, std::size_t want) {
const auto checkLength = [&](const char* name, std::size_t got,
std::size_t want) {
if (got != want) {
fail(std::string(name) + " has length " + std::to_string(got) +
", expected " + std::to_string(want));
", expected " + std::to_string(want));
return false;
}
return true;
};
if (!checkLength("positions", actual.positions.size(),
expected.positions.size()) ||
!checkLength("scales", actual.scales.size(),
expected.scales.size()) ||
expected.positions.size()) ||
!checkLength("scales", actual.scales.size(), expected.scales.size()) ||
!checkLength("rotations", actual.rotations.size(),
expected.rotations.size()) ||
expected.rotations.size()) ||
!checkLength("opacities", actual.opacities.size(),
expected.opacities.size()) ||
expected.opacities.size()) ||
!checkLength("dcCoefficients", actual.dcCoefficients.size(),
expected.dcCoefficients.size()) ||
expected.dcCoefficients.size()) ||
!checkLength("restCoefficients", actual.restCoefficients.size(),
expected.restCoefficients.size())) {
expected.restCoefficients.size())) {
return mismatches;
}

const auto close = [](float a, float b, float tolerance) {
return std::fabs(a - b) <= tolerance;
};
const auto checkFloat3 = [&](
const char* name, std::size_t index,
const Float3& got, const Float3& want, float tolerance) {
const auto checkFloat3 = [&](const char* name, std::size_t index,
const Float3& got, const Float3& want,
float tolerance) {
if (!close(got.x, want.x, tolerance) ||
!close(got.y, want.y, tolerance) ||
!close(got.z, want.z, tolerance)) {
fail(std::string(name) + "[" + std::to_string(index) + "] (" +
std::to_string(got.x) + ", " + std::to_string(got.y) + ", " +
std::to_string(got.z) + ") != expected (" +
std::to_string(want.x) + ", " + std::to_string(want.y) +
", " + std::to_string(want.z) + ")");
std::to_string(got.x) + ", " + std::to_string(got.y) + ", " +
std::to_string(got.z) + ") != expected (" +
std::to_string(want.x) + ", " + std::to_string(want.y) + ", " +
std::to_string(want.z) + ")");
}
};

for (std::size_t i = 0; i < actual.gaussianCount; ++i) {
checkFloat3("positions", i, actual.positions[i],
expected.positions[i], tolerances.position);
checkFloat3("scales", i, actual.scales[i],
expected.scales[i], tolerances.scale);
if (!QuaternionsEquivalent(actual.rotations[i],
expected.rotations[i], tolerances.rotation)) {
checkFloat3("positions", i, actual.positions[i], expected.positions[i],
tolerances.position);
checkFloat3("scales", i, actual.scales[i], expected.scales[i],
tolerances.scale);
if (!QuaternionsEquivalent(actual.rotations[i], expected.rotations[i],
tolerances.rotation)) {
fail("rotations[" + std::to_string(i) +
"] differs beyond tolerance under either sign");
"] differs beyond tolerance under either sign");
}
if (!close(actual.opacities[i], expected.opacities[i],
tolerances.opacity)) {
tolerances.opacity)) {
fail("opacities[" + std::to_string(i) + "] " +
std::to_string(actual.opacities[i]) + " != expected " +
std::to_string(expected.opacities[i]));
std::to_string(actual.opacities[i]) + " != expected " +
std::to_string(expected.opacities[i]));
}
checkFloat3("dcCoefficients", i, actual.dcCoefficients[i],
expected.dcCoefficients[i], tolerances.shCoefficient);
expected.dcCoefficients[i], tolerances.shCoefficient);
}

const std::size_t restPerGaussian =
expected.gaussianCount == 0
? 0
: expected.restCoefficients.size() / expected.gaussianCount;
expected.gaussianCount == 0 ?
0 :
expected.restCoefficients.size() / expected.gaussianCount;
for (std::size_t i = 0; i < actual.restCoefficients.size(); ++i) {
const std::size_t gaussian =
restPerGaussian == 0 ? 0 : i / restPerGaussian;
Expand All @@ -217,11 +216,11 @@ inline std::vector<std::string> CompareClouds(
!close(got.y, want.y, tolerances.shCoefficient) ||
!close(got.z, want.z, tolerances.shCoefficient)) {
fail("restCoefficients[gaussian " + std::to_string(gaussian) +
", coefficient " + std::to_string(coefficient) + "] (" +
std::to_string(got.x) + ", " + std::to_string(got.y) + ", " +
std::to_string(got.z) + ") != expected (" +
std::to_string(want.x) + ", " + std::to_string(want.y) +
", " + std::to_string(want.z) + ")");
", coefficient " + std::to_string(coefficient) + "] (" +
std::to_string(got.x) + ", " + std::to_string(got.y) + ", " +
std::to_string(got.z) + ") != expected (" +
std::to_string(want.x) + ", " + std::to_string(want.y) + ", " +
std::to_string(want.z) + ")");
}
}

Expand All @@ -241,8 +240,8 @@ inline std::vector<std::string> CompareClouds(
} else if (actualHasExtent) {
const auto extentClose = [&](const Float3& a, const Float3& b) {
return close(a.x, b.x, tolerances.extent) &&
close(a.y, b.y, tolerances.extent) &&
close(a.z, b.z, tolerances.extent);
close(a.y, b.y, tolerances.extent) &&
close(a.z, b.z, tolerances.extent);
};
if (!extentClose(actualMinimum, expectedMinimum) ||
!extentClose(actualMaximum, expectedMaximum)) {
Expand Down Expand Up @@ -325,8 +324,7 @@ inline std::vector<InvalidCloudCase> MakeInvalidCloudCases()
// Degree above the supported ceiling, rest sized consistently for it.
GaussianCloudData cloud = base;
cloud.shDegree = kMaxShDegree + 1;
cloud.restCoefficients.resize(
cloud.CoefficientsPerGaussian() - 1);
cloud.restCoefficients.resize(cloud.CoefficientsPerGaussian() - 1);
add("unsupported-sh-degree", std::move(cloud));
}
{
Expand Down
27 changes: 14 additions & 13 deletions libs/gaussian-core/src/GaussianImportStats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ CoordinateConversionName(GaussianCoordinateConversion conversion) noexcept
std::uint64_t ComputeDecodedByteSize(const GaussianCloudData& cloud) noexcept
{
return static_cast<std::uint64_t>(cloud.positions.size()) * sizeof(Float3) +
static_cast<std::uint64_t>(cloud.scales.size()) * sizeof(Float3) +
static_cast<std::uint64_t>(cloud.rotations.size()) * sizeof(Quaternion) +
static_cast<std::uint64_t>(cloud.opacities.size()) * sizeof(float) +
static_cast<std::uint64_t>(cloud.dcCoefficients.size()) * sizeof(Float3) +
static_cast<std::uint64_t>(cloud.restCoefficients.size()) * sizeof(Float3);
static_cast<std::uint64_t>(cloud.scales.size()) * sizeof(Float3) +
static_cast<std::uint64_t>(cloud.rotations.size()) *
sizeof(Quaternion) +
static_cast<std::uint64_t>(cloud.opacities.size()) * sizeof(float) +
static_cast<std::uint64_t>(cloud.dcCoefficients.size()) *
sizeof(Float3) +
static_cast<std::uint64_t>(cloud.restCoefficients.size()) *
sizeof(Float3);
}

std::string FormatImportStats(const GaussianImportStats& stats)
Expand Down Expand Up @@ -73,14 +76,12 @@ std::string FormatImportStats(const GaussianImportStats& stats)
add("sourceBytes", std::to_string(stats.sourceBytes));
add("decodedBytes", std::to_string(stats.decodedBytes));
if (stats.hasBounds) {
add("boundsMin",
FormatDouble(stats.boundsMinimum.x) + ',' +
FormatDouble(stats.boundsMinimum.y) + ',' +
FormatDouble(stats.boundsMinimum.z));
add("boundsMax",
FormatDouble(stats.boundsMaximum.x) + ',' +
FormatDouble(stats.boundsMaximum.y) + ',' +
FormatDouble(stats.boundsMaximum.z));
add("boundsMin", FormatDouble(stats.boundsMinimum.x) + ',' +
FormatDouble(stats.boundsMinimum.y) + ',' +
FormatDouble(stats.boundsMinimum.z));
add("boundsMax", FormatDouble(stats.boundsMaximum.x) + ',' +
FormatDouble(stats.boundsMaximum.y) + ',' +
FormatDouble(stats.boundsMaximum.z));
}
add("readSeconds", FormatDouble(stats.readSeconds));
add("decodeSeconds", FormatDouble(stats.decodeSeconds));
Expand Down
Loading
Loading