gsplat 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +236 -0
- data/docs/ACCEPTANCE.md +60 -0
- data/docs/BENCHMARKS.md +79 -0
- data/docs/DECISIONS.md +52 -0
- data/docs/MIGRATION.md +117 -0
- data/docs/PROFILE.md +75 -0
- data/docs/PROGRESS.md +199 -0
- data/docs/decisions/0000-template.md +17 -0
- data/docs/decisions/0001-pin-golden-reference-and-cpu-validation.md +27 -0
- data/docs/decisions/0002-retain-ruby-fallbacks-for-native-backend.md +26 -0
- data/docs/decisions/0003-share-projection-for-distorted-cameras.md +26 -0
- data/docs/decisions/0004-use-portable-world-space-reference-paths.md +26 -0
- data/docs/decisions/0005-reuse-ewa-core-for-2dgs.md +24 -0
- data/docs/decisions/0006-keep-eval3d-as-portable-reference.md +25 -0
- data/docs/decisions/0007-share-compositor-semantics-for-contribution-indices.md +25 -0
- data/examples/data/README.md +14 -0
- data/examples/data/colmap/images/view_000.png +0 -0
- data/examples/data/colmap/images/view_001.png +0 -0
- data/examples/data/colmap/images/view_002.png +0 -0
- data/examples/data/colmap/sparse/0/cameras.txt +2 -0
- data/examples/data/colmap/sparse/0/images.txt +6 -0
- data/examples/data/colmap/sparse/0/points3D.txt +17 -0
- data/examples/data/splats.ply +0 -0
- data/examples/fit_image.rb +35 -0
- data/examples/generate_sample_data.rb +147 -0
- data/examples/render_path.rb +117 -0
- data/examples/simple_trainer.rb +76 -0
- data/ext/gsplat_native/common.h +54 -0
- data/ext/gsplat_native/extconf.rb +39 -0
- data/ext/gsplat_native/gsplat_native.c +60 -0
- data/ext/gsplat_native/intersections.c +211 -0
- data/ext/gsplat_native/projection.c +199 -0
- data/ext/gsplat_native/raster_backward.c +129 -0
- data/ext/gsplat_native/raster_backward_bridge.c +69 -0
- data/ext/gsplat_native/raster_forward.c +150 -0
- data/ext/gsplat_native/rasterization.h +51 -0
- data/ext/gsplat_native/spherical_harmonics.c +129 -0
- data/gsplat.gemspec +36 -0
- data/lib/gsplat/autograd/context.rb +60 -0
- data/lib/gsplat/autograd/function.rb +68 -0
- data/lib/gsplat/autograd/variable.rb +159 -0
- data/lib/gsplat/backend/ruby/accumulate.rb +139 -0
- data/lib/gsplat/backend/ruby/accumulate_backward.rb +40 -0
- data/lib/gsplat/backend/ruby/eval3d_rasterizer.rb +175 -0
- data/lib/gsplat/backend/ruby/isect_tiles.rb +198 -0
- data/lib/gsplat/backend/ruby/projection.rb +251 -0
- data/lib/gsplat/backend/ruby/projection_backward.rb +190 -0
- data/lib/gsplat/backend/ruby/projection_covariance_vjp.rb +72 -0
- data/lib/gsplat/backend/ruby/projection_input_vjp.rb +252 -0
- data/lib/gsplat/backend/ruby/quat_scale_to_covar_preci.rb +139 -0
- data/lib/gsplat/backend/ruby/rasterize_to_indices_in_range.rb +121 -0
- data/lib/gsplat/backend/ruby/rasterize_to_pixels.rb +199 -0
- data/lib/gsplat/backend/ruby/rasterize_to_pixels_backward.rb +121 -0
- data/lib/gsplat/backend/ruby/spherical_harmonics.rb +135 -0
- data/lib/gsplat/backend/ruby/tile_compositor.rb +50 -0
- data/lib/gsplat/backend/ruby/tile_compositor_backward.rb +84 -0
- data/lib/gsplat/backend.rb +79 -0
- data/lib/gsplat/compression/grid_sort.rb +79 -0
- data/lib/gsplat/compression/kmeans.rb +121 -0
- data/lib/gsplat/compression/png.rb +147 -0
- data/lib/gsplat/compression/png_codec.rb +133 -0
- data/lib/gsplat/compression/quantizer.rb +79 -0
- data/lib/gsplat/io/checkpoint.rb +145 -0
- data/lib/gsplat/io/colmap.rb +175 -0
- data/lib/gsplat/io/colmap_binary.rb +98 -0
- data/lib/gsplat/io/colmap_text.rb +84 -0
- data/lib/gsplat/io/image.rb +63 -0
- data/lib/gsplat/io/image_backends.rb +81 -0
- data/lib/gsplat/io/npy.rb +189 -0
- data/lib/gsplat/io/ply.rb +185 -0
- data/lib/gsplat/io/ply_reader.rb +142 -0
- data/lib/gsplat/io/zip_archive.rb +183 -0
- data/lib/gsplat/math/camera_distortion.rb +123 -0
- data/lib/gsplat/math/camera_projection.rb +202 -0
- data/lib/gsplat/math/mat.rb +114 -0
- data/lib/gsplat/math/quaternion.rb +175 -0
- data/lib/gsplat/math/small_matrix_primitives.rb +112 -0
- data/lib/gsplat/math/spherical_harmonic_basis.rb +148 -0
- data/lib/gsplat/math/ssim.rb +153 -0
- data/lib/gsplat/native.rb +30 -0
- data/lib/gsplat/native_ops.rb +148 -0
- data/lib/gsplat/native_raster_ops.rb +76 -0
- data/lib/gsplat/ops/accumulate.rb +68 -0
- data/lib/gsplat/ops/eval3d_rasterize.rb +62 -0
- data/lib/gsplat/ops/isect_tiles.rb +38 -0
- data/lib/gsplat/ops/projection.rb +154 -0
- data/lib/gsplat/ops/quat_scale_to_covar_preci.rb +118 -0
- data/lib/gsplat/ops/rasterize_to_indices_in_range.rb +38 -0
- data/lib/gsplat/ops/rasterize_to_pixels.rb +105 -0
- data/lib/gsplat/ops/relocation.rb +94 -0
- data/lib/gsplat/ops/spherical_harmonics.rb +71 -0
- data/lib/gsplat/ops/tensor_shape_ops.rb +173 -0
- data/lib/gsplat/ops/tensor_value_ops.rb +150 -0
- data/lib/gsplat/optim/adam.rb +212 -0
- data/lib/gsplat/optim/lr_scheduler.rb +36 -0
- data/lib/gsplat/optim/selective_adam.rb +68 -0
- data/lib/gsplat/rasterization.rb +130 -0
- data/lib/gsplat/rasterization_2dgs.rb +140 -0
- data/lib/gsplat/rasterization_helpers.rb +162 -0
- data/lib/gsplat/rasterization_validation.rb +99 -0
- data/lib/gsplat/strategy/base.rb +49 -0
- data/lib/gsplat/strategy/default.rb +188 -0
- data/lib/gsplat/strategy/mcmc.rb +103 -0
- data/lib/gsplat/strategy/mcmc_ops.rb +143 -0
- data/lib/gsplat/strategy/ops.rb +165 -0
- data/lib/gsplat/training/config.rb +91 -0
- data/lib/gsplat/training/image_fitter.rb +158 -0
- data/lib/gsplat/training/losses.rb +191 -0
- data/lib/gsplat/training/scene.rb +116 -0
- data/lib/gsplat/training/trainer.rb +236 -0
- data/lib/gsplat/utils.rb +110 -0
- data/lib/gsplat/version.rb +6 -0
- data/lib/gsplat.rb +98 -0
- metadata +181 -0
data/docs/MIGRATION.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Migrating from Python gsplat
|
|
2
|
+
|
|
3
|
+
The Ruby API keeps upstream operation names and tensor layouts where Numo::NArray can represent them
|
|
4
|
+
directly. The main changes are Ruby keyword syntax, an explicit lightweight autograd wrapper, and a
|
|
5
|
+
CPU-only dense execution model.
|
|
6
|
+
|
|
7
|
+
## Rendering call
|
|
8
|
+
|
|
9
|
+
Python:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
rendered, alphas, meta = rasterization(
|
|
13
|
+
means, quats, scales, opacities, colors, viewmats, Ks, width, height,
|
|
14
|
+
render_mode="RGB", packed=False,
|
|
15
|
+
)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Ruby:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
rendered, alphas, meta = Gsplat.rasterization(
|
|
22
|
+
means: means,
|
|
23
|
+
quats: quats,
|
|
24
|
+
scales: scales,
|
|
25
|
+
opacities: opacities,
|
|
26
|
+
colors: colors,
|
|
27
|
+
viewmats: viewmats,
|
|
28
|
+
ks: intrinsics,
|
|
29
|
+
width: width,
|
|
30
|
+
height: height,
|
|
31
|
+
render_mode: "RGB",
|
|
32
|
+
packed: false
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`Ks` becomes the idiomatic keyword `ks`. Hash keys in metadata are symbols, for example
|
|
37
|
+
`meta[:means2d]`, `meta[:radii]`, and `meta[:isect_offsets]`.
|
|
38
|
+
|
|
39
|
+
## Tensor and autograd mapping
|
|
40
|
+
|
|
41
|
+
| PyTorch | Ruby |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `torch.float32` / `torch.float64` | `Numo::SFloat` / `Numo::DFloat` |
|
|
44
|
+
| `tensor.requires_grad_(True)` | `Autograd::Variable.new(array, requires_grad: true)` |
|
|
45
|
+
| `tensor.detach()` | `variable.data` |
|
|
46
|
+
| `tensor.grad` | `variable.grad` |
|
|
47
|
+
| `optimizer.zero_grad()` | `optimizer.zero_grad!` or `variable.zero_grad!` |
|
|
48
|
+
| `with torch.no_grad()` | `Gsplat::Autograd.no_grad { ... }` |
|
|
49
|
+
| `loss.backward()` | `loss.backward` |
|
|
50
|
+
| `output.backward(weight)` | `output.backward(weight)` |
|
|
51
|
+
|
|
52
|
+
Numo arrays do not carry device or graph state. Only `Autograd::Variable` does. Mixed calls return
|
|
53
|
+
Variables when any differentiable input requires gradients, otherwise they return raw Numo arrays.
|
|
54
|
+
|
|
55
|
+
The package supports camera batches `[C,...]`, not arbitrary leading batch dimensions. It uses dense
|
|
56
|
+
`[C,N,...]` intermediates. `packed: true` is accepted for call compatibility but warns and executes
|
|
57
|
+
dense mode; sparse gradients are unavailable.
|
|
58
|
+
|
|
59
|
+
## Geometry and activation
|
|
60
|
+
|
|
61
|
+
- Quaternion order is `wxyz`.
|
|
62
|
+
- Rendering takes activated `scales > 0` and `opacities` in the 0–1 range.
|
|
63
|
+
- Trainer parameter hashes store `:scales` as log-scales and `:opacities` as logits, matching the
|
|
64
|
+
optimization convention in the upstream examples.
|
|
65
|
+
- Direct `covars [N,3,3]` or packed symmetric `covars [N,6]` replace `quats`/`scales` on the normal
|
|
66
|
+
2D raster path.
|
|
67
|
+
- Views are world-to-camera `[C,4,4]`; camera forward is positive Z.
|
|
68
|
+
|
|
69
|
+
## Render modes and metadata
|
|
70
|
+
|
|
71
|
+
The following modes are available: `RGB`, `D`, `ED`, `RGB+D`, `RGB+ED`, `d`, `Ed`, `RGB-d`, and
|
|
72
|
+
`RGB-Ed`. Uppercase depth uses projected Gaussian depth; lowercase depth is per-ray hit distance.
|
|
73
|
+
|
|
74
|
+
`absgrad: true` updates `meta[:means2d_absgrad]` after backward. World-space normals requested through
|
|
75
|
+
`with_eval3d: true, return_normals: true` are returned as `meta[:normals]`.
|
|
76
|
+
|
|
77
|
+
## Optimizers and strategies
|
|
78
|
+
|
|
79
|
+
An upstream parameter dictionary maps naturally to a Ruby symbol-keyed Hash:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
params = {
|
|
83
|
+
means: means_variable,
|
|
84
|
+
quats: quats_variable,
|
|
85
|
+
scales: log_scales_variable,
|
|
86
|
+
opacities: logits_variable,
|
|
87
|
+
sh0: direct_sh_variable,
|
|
88
|
+
shN: remaining_sh_variable
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`Gsplat::Strategy::Default` and `Gsplat::Strategy::MCMC` use the same lifecycle around backward. All
|
|
93
|
+
structural edits must go through `Gsplat::Strategy::Ops` so parameter rows and Adam moments remain
|
|
94
|
+
aligned. `Gsplat::Optim::SelectiveAdam` accepts a first-axis visibility mask for visible-only updates.
|
|
95
|
+
|
|
96
|
+
## IO mapping
|
|
97
|
+
|
|
98
|
+
| Python workflow | Ruby API |
|
|
99
|
+
|---|---|
|
|
100
|
+
| COLMAP parser | `Gsplat::IO::Colmap.read` |
|
|
101
|
+
| Inria PLY export/import | `Gsplat::IO::Ply.write` / `.read` |
|
|
102
|
+
| training checkpoint | `Gsplat::IO::Checkpoint.save` / `.restore!` |
|
|
103
|
+
| NumPy fixture exchange | `Gsplat::IO::Npy.read`, `.write`, `.read_npz`, `.write_npz` |
|
|
104
|
+
| PNG parameter compression | `Gsplat::Compression::Png#compress` / `#decompress` |
|
|
105
|
+
|
|
106
|
+
NPY/NPZ supports the dtypes used by this project and C-order arrays. It intentionally does not load
|
|
107
|
+
arbitrary NumPy object arrays or Fortran-order payloads.
|
|
108
|
+
|
|
109
|
+
## Unsupported upstream options
|
|
110
|
+
|
|
111
|
+
There is no CUDA device backend, distributed rendering, arbitrary leading batch support, packed/sparse
|
|
112
|
+
execution, F-theta/lidar cameras, rolling shutter, custom rays, unscented-transform projection,
|
|
113
|
+
renderer configs, extra signals, or LPIPS in version 1.0.
|
|
114
|
+
|
|
115
|
+
The current 2DGS auxiliary buffers are compatible in shape but approximate upstream ray-splat
|
|
116
|
+
geometry. Eval3d is pinhole/classic only and uses a numerical geometry VJP. Check
|
|
117
|
+
`docs/DECISIONS.md` when exact cross-implementation parity is required.
|
data/docs/PROFILE.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Performance Profile
|
|
2
|
+
|
|
3
|
+
## Ruby baseline — 2026-07-23
|
|
4
|
+
|
|
5
|
+
Command:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bundle exec ruby -Ilib benchmarks/profile_ruby.rb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Environment: CRuby 4.0.0, arm64-darwin24. Deterministic scene: 1,000 Gaussians, one 64×48
|
|
12
|
+
camera, SH degree 3, five measured iterations after one warm-up.
|
|
13
|
+
|
|
14
|
+
| Operation | Mean time |
|
|
15
|
+
|---|---:|
|
|
16
|
+
| Projection | 1.010 ms |
|
|
17
|
+
| Spherical harmonics | 0.393 ms |
|
|
18
|
+
| Tile intersections and sort | 2.505 ms |
|
|
19
|
+
| Rasterization forward | 58.350 ms |
|
|
20
|
+
|
|
21
|
+
Rasterization accounts for about 93.7% of the summed operation time. Tile intersection generation
|
|
22
|
+
is the second-largest component. This satisfies the P10 profiling prerequisite and fixes the native
|
|
23
|
+
implementation order: bridge first, projection/SH for coverage, intersection sort, then the
|
|
24
|
+
dominant raster forward/backward path. These numbers are a small-scene attribution profile, not
|
|
25
|
+
the final P12 throughput benchmark.
|
|
26
|
+
|
|
27
|
+
## Native projection and SH — 2026-07-23
|
|
28
|
+
|
|
29
|
+
With the P10-2 extension loaded (`GSPLAT_BACKEND=auto`) on the same workload:
|
|
30
|
+
|
|
31
|
+
| Operation | Ruby | Native/auto | Speedup |
|
|
32
|
+
|---|---:|---:|---:|
|
|
33
|
+
| Projection | 1.010 ms | 0.257 ms | 3.9× |
|
|
34
|
+
| Spherical harmonics | 0.393 ms | 0.043 ms | 9.1× |
|
|
35
|
+
|
|
36
|
+
The C paths cover contiguous float32 forward calls. Float64, masked SH, and analytic backward use
|
|
37
|
+
the Ruby implementation to preserve the existing numerical behavior.
|
|
38
|
+
|
|
39
|
+
## Native intersections — 2026-07-23
|
|
40
|
+
|
|
41
|
+
The P10-3 kernel adds float32 tile-bound enumeration, 64-bit key generation, stable LSD radix sort,
|
|
42
|
+
and tile-offset encoding. It retains the same key layout and ordering as the Ruby implementation.
|
|
43
|
+
|
|
44
|
+
| Operation | Ruby | Native/auto | Speedup |
|
|
45
|
+
|---|---:|---:|---:|
|
|
46
|
+
| Tile intersections and sort | 2.505 ms | 0.045 ms | 55.7× |
|
|
47
|
+
|
|
48
|
+
The measurement uses the same 1,000-Gaussian workload as the baseline. Float64 inputs retain the
|
|
49
|
+
Ruby path so numerical gradient tests continue to exercise the reference implementation.
|
|
50
|
+
|
|
51
|
+
## Native rasterization — 2026-07-23
|
|
52
|
+
|
|
53
|
+
The float32 forward and backward tile kernels release the GVL. Forward owns disjoint pixel tiles.
|
|
54
|
+
Backward uses atomic scatter-add under OpenMP; this keeps memory at `O(N × D)` rather than allocating
|
|
55
|
+
one full gradient buffer per worker. On the 1,000-Gaussian, 64×48 profile:
|
|
56
|
+
|
|
57
|
+
| Operation | Ruby | Native (1 thread) | Speedup |
|
|
58
|
+
|---|---:|---:|---:|
|
|
59
|
+
| Raster forward | 55.821 ms | 2.284 ms | 24.4× |
|
|
60
|
+
| Raster backward + absgrad | 136.854 ms | 2.700 ms | 50.7× |
|
|
61
|
+
|
|
62
|
+
The required 100,000-Gaussian, 800×800 float32 workload gives:
|
|
63
|
+
|
|
64
|
+
| OpenMP threads | Forward | Backward + absgrad | Combined |
|
|
65
|
+
|---:|---:|---:|---:|
|
|
66
|
+
| 1 | 114.012 ms | 279.790 ms | 393.802 ms |
|
|
67
|
+
| 2 | 57.751 ms | 148.111 ms | 205.862 ms |
|
|
68
|
+
| 4 | 30.446 ms | 83.138 ms | 113.584 ms |
|
|
69
|
+
| 8 | 29.643 ms | 79.252 ms | 108.895 ms |
|
|
70
|
+
|
|
71
|
+
This meets the 150 ms forward and 400 ms combined design targets even at one thread. The measured
|
|
72
|
+
3.5× backward scaling from one to eight workers supports the bounded-memory atomic strategy. Scaling
|
|
73
|
+
flattens after four workers on this Apple Silicon host, so higher thread counts are not assumed to
|
|
74
|
+
improve throughput. Homebrew `libomp` is auto-detected on Apple Clang; other builds remain functional
|
|
75
|
+
without OpenMP.
|
data/docs/PROGRESS.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Implementation Progress
|
|
2
|
+
|
|
3
|
+
This file is the restart point for implementation sessions. Read it after `README.md`.
|
|
4
|
+
|
|
5
|
+
| Task | Status | Completed | Notes |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| P0-1 | Complete | 2026-07-23 | Gem foundation; 4 tests, 7 assertions |
|
|
8
|
+
| P0-2 | Complete | 2026-07-23 | Backend registry, selection, environment override, one-time fallback warning |
|
|
9
|
+
| P0-3 | Complete | 2026-07-23 | NPY v1.0 and stored/deflated NPZ; NumPy 2.3.1 interoperability verified |
|
|
10
|
+
| P0-4 | Complete (alternate DoD) | 2026-07-23 | 47-case generator and dependency-free dry run; CUDA goldens pending CUDA host |
|
|
11
|
+
| P0-5 | Complete | 2026-07-23 | allclose/golden/backend test helpers and Ruby 3.2–4.0 CI matrix |
|
|
12
|
+
| P1-1 | Complete | 2026-07-23 | Autograd Variable/Function/Context, branching, multi-output, no_grad, graph release |
|
|
13
|
+
| P1-2 | Complete | 2026-07-23 | Batched quaternion VJPs and closed-form 2x2/3x3 matrix operations |
|
|
14
|
+
| P1-3 | Complete (golden pending) | 2026-07-23 | Covariance/precision fwd+bwd, triu output, float64 gradcheck |
|
|
15
|
+
| P2-1 | Complete (golden pending) | 2026-07-23 | SH degrees 0–4, masks, arbitrary channels, analytic direction/coefficient VJPs |
|
|
16
|
+
| P3-1 | Complete (golden pending) | 2026-07-23 | Pinhole forward, world/camera primitives, culling and compensation |
|
|
17
|
+
| P3-2 | Complete (golden pending) | 2026-07-23 | Analytic projection VJPs for means, covariance, quaternion and scale |
|
|
18
|
+
| P3-3 | Complete (golden pending) | 2026-07-23 | Orthographic forward/backward and camera-model dispatch |
|
|
19
|
+
| P4-1 | Complete (golden pending) | 2026-07-23 | Tile AABBs, 64-bit intersection keys, sorting and prefix offsets |
|
|
20
|
+
| P5-1 | Complete | 2026-07-23 | Brute-force alpha compositor with arbitrary channels and backgrounds |
|
|
21
|
+
| P5-2 | Complete (golden pending) | 2026-07-23 | Vectorized tile compositor, masks, partial edge tiles and retained last ids |
|
|
22
|
+
| P6-1 | Complete (golden pending) | 2026-07-23 | Reverse tile scan VJPs and brute-force gradient cross-check |
|
|
23
|
+
| P6-2 | Complete (golden pending) | 2026-07-23 | Optional per-pixel absolute mean-gradient accumulation |
|
|
24
|
+
| P7-1 | Complete (golden pending) | 2026-07-23 | High-level render modes, SH, antialiasing, depth normalization and metadata |
|
|
25
|
+
| P7-2 | Complete | 2026-07-23 | Self-consistency image fitter, 128px fixture and monotonic PSNR E2E |
|
|
26
|
+
| P8-1 | Complete | 2026-07-23 | Adam groups, bias correction, editable state and exponential scheduling |
|
|
27
|
+
| P8-2 | Complete | 2026-07-23 | Strategy lifecycle and synchronized duplicate/split/remove/reset operations |
|
|
28
|
+
| P8-3 | Complete (golden pending) | 2026-07-23 | Default densification statistics, growth, pruning and opacity reset |
|
|
29
|
+
| P8-4 | Complete (golden pending) | 2026-07-23 | Closed-form relocation, weighted sampling, covariance noise and MCMC strategy |
|
|
30
|
+
| P9-1 | Complete | 2026-07-23 | Inria PLY writer and arbitrary-order ASCII/binary little-endian reader |
|
|
31
|
+
| P9-2 | Complete | 2026-07-23 | COLMAP bin/txt cameras, images and 100-point fixture with pose conversion |
|
|
32
|
+
| P9-3 | Complete (golden pending) | 2026-07-23 | k-NN initialization, SH color helpers, scene scale and differentiable SSIM |
|
|
33
|
+
| P9-4 | Complete | 2026-07-23 | NPZ training checkpoints and vips/chunky_png RGB image IO |
|
|
34
|
+
| P9-5 | Complete | 2026-07-23 | Multi-view Trainer, staged SH, strategy hooks and training/render CLIs |
|
|
35
|
+
| P10-1 | Complete | 2026-07-23 | Optional C extension, Numo bridge, GVL release and build fallback |
|
|
36
|
+
| P10-2 | Complete (hybrid) | 2026-07-23 | C float32 projection/SH forward; analytic backward and float64 fallback |
|
|
37
|
+
| P10-3 | Complete | 2026-07-23 | C tile enumeration, stable 64-bit radix sort and offset encoding |
|
|
38
|
+
| P10-4 | Complete | 2026-07-23 | GVL-free OpenMP raster forward/backward with atomic scatter-add |
|
|
39
|
+
| P11-1 | Complete (golden pending) | 2026-07-23 | N-D features, differentiable channel chunking and radius clipping |
|
|
40
|
+
| P11-2 | Complete (golden pending) | 2026-07-23 | Full and packed direct covariance forward/backward across the renderer |
|
|
41
|
+
| P11-3 | Complete (golden pending) | 2026-07-23 | Axis-aligned elliptical radii in Ruby/native projection and metadata |
|
|
42
|
+
| P11-4 | Complete | 2026-07-23 | Morton grid sort, PNG quantization, SH K-means and compatible metadata layout |
|
|
43
|
+
| P11-5 | Complete (golden pending) | 2026-07-23 | Fisheye and OpenCV radial/tangential/thin-prism projection with VJPs |
|
|
44
|
+
| P11-6 | Complete (golden pending) | 2026-07-24 | Euclidean center sorting and per-pixel anisotropic ray-hit distance modes |
|
|
45
|
+
| P11-7 | Complete | 2026-07-24 | First-axis visibility-masked Adam parameter and moment updates |
|
|
46
|
+
| P11-8 | Complete (golden pending) | 2026-07-24 | 2DGS API, auxiliary geometry buffers and Trainer mode |
|
|
47
|
+
| P11-9 | Complete (golden pending) | 2026-07-24 | World-space color evaluation, ray-facing accumulated normals and numerical VJPs |
|
|
48
|
+
| P11-10 | Complete (golden pending) | 2026-07-24 | Iterative per-pixel Gaussian contribution-index enumeration |
|
|
49
|
+
| P12-1 | Complete | 2026-07-24 | README/Migration guide, executable quick start, example smoke tests and 100% public YARD coverage |
|
|
50
|
+
| P12-2 | Complete (external gates recorded) | 2026-07-24 | Design workloads, benchmark results, enabled CI matrix and G1–G6 acceptance report |
|
|
51
|
+
| P12-3 | Complete | 2026-07-24 | Version 1.0.0, Apache-2.0, release metadata, gem build and clean native install/render verified |
|
|
52
|
+
|
|
53
|
+
## Phase gates
|
|
54
|
+
|
|
55
|
+
### P0 — Complete (alternate golden-data gate)
|
|
56
|
+
|
|
57
|
+
- Full suite: 25 tests, 91 assertions, no failures or skips.
|
|
58
|
+
- RuboCop: no offenses.
|
|
59
|
+
- Golden status: generator dry-run and syntax checks pass; complete CUDA output remains pending per
|
|
60
|
+
`tools/README.md` because this host has no CUDA and the temporary PyTorch install exceeded disk capacity.
|
|
61
|
+
- Design differences: recorded in `docs/DECISIONS.md`.
|
|
62
|
+
|
|
63
|
+
### P1 — Complete (golden-data gate pending)
|
|
64
|
+
|
|
65
|
+
- L1/L2: analytic properties and float64 central differences pass.
|
|
66
|
+
- L3: `quat_covar_full.npz` test is present and skipped until `tools/README.md` CUDA generation is run.
|
|
67
|
+
- Full suite and lint evidence are recorded in the P1-3 commit.
|
|
68
|
+
|
|
69
|
+
### P2 — Complete (golden-data gate pending)
|
|
70
|
+
|
|
71
|
+
- L1/L2: known degree-zero/degree-one values and float64 direction/coefficient central differences pass.
|
|
72
|
+
- Masks suppress forward values and their corresponding VJPs; channels are not restricted to RGB.
|
|
73
|
+
- L3: `sh_deg3.npz` test is present and skipped until `tools/README.md` golden generation is run.
|
|
74
|
+
- Full suite: 52 tests, 130 assertions, no failures, 2 documented golden-data skips.
|
|
75
|
+
- RuboCop: no offenses.
|
|
76
|
+
|
|
77
|
+
### P3 — Complete (golden-data gate pending)
|
|
78
|
+
|
|
79
|
+
- L1/L2: hand-calculated pinhole/orthographic cases and float64 central differences pass.
|
|
80
|
+
- Covariance-direct and quaternion/scale paths both propagate analytic gradients.
|
|
81
|
+
- L3: pinhole and orthographic tests are present and skip until golden generation is run.
|
|
82
|
+
- Projection radii use the upstream `[C,N,2]` axis-aligned elliptical representation.
|
|
83
|
+
- Full suite: 63 tests, 155 assertions, no failures, 5 documented golden-data skips.
|
|
84
|
+
- RuboCop: no offenses.
|
|
85
|
+
|
|
86
|
+
### P4 — Complete (golden-data gate pending)
|
|
87
|
+
|
|
88
|
+
- L1: exact handcrafted tile counts, 64-bit keys, flattened ids and empty-tile offsets pass.
|
|
89
|
+
- Elliptical radii are primary; legacy scalar radii remain accepted by the low-level intersection API.
|
|
90
|
+
- L3: `isect_c3_n1000.npz` coverage is present and skips until golden generation is run.
|
|
91
|
+
- Full suite: 68 tests, 163 assertions, no failures, 6 documented golden-data skips.
|
|
92
|
+
- RuboCop: no offenses.
|
|
93
|
+
|
|
94
|
+
### P5 — Complete (golden-data gate pending)
|
|
95
|
+
|
|
96
|
+
- L1/L4: single-pixel analytical cases and tile-vs-brute-force image comparisons pass.
|
|
97
|
+
- Partial edge tiles, arbitrary channels, backgrounds and tile masks are covered.
|
|
98
|
+
- L3: `raster_rgb.npz` forward coverage is present and skips until CUDA golden generation is run.
|
|
99
|
+
- Full suite: 75 tests, 175 assertions, no failures, 7 documented golden-data skips.
|
|
100
|
+
- RuboCop: no offenses.
|
|
101
|
+
|
|
102
|
+
### P6 — Complete (golden-data gate pending)
|
|
103
|
+
|
|
104
|
+
- L2: float64 central differences pass for means, conics, colors, opacities and backgrounds.
|
|
105
|
+
- L4: tile and brute-force backward gradients match exactly on the reference scene.
|
|
106
|
+
- `absgrad` accumulates absolute per-pixel mean contributions and allocates no buffer when disabled.
|
|
107
|
+
- L3: raster gradients and absolute gradients skip until CUDA golden generation is run.
|
|
108
|
+
- Full suite: 81 tests, 190 assertions, no failures, 9 documented golden-data skips.
|
|
109
|
+
- RuboCop: no offenses.
|
|
110
|
+
|
|
111
|
+
### P7 — Complete (golden-data gate pending)
|
|
112
|
+
|
|
113
|
+
- High-level RGB/depth modes, SH, antialiasing, metadata and end-to-end gradients pass.
|
|
114
|
+
- The reduced image-fit E2E improves monotonically from 11.77 dB to 28.89 dB in 12 steps.
|
|
115
|
+
- A 128×128 PPM fixture and a configurable 2,000-Gaussian/300-step example are included.
|
|
116
|
+
- L3 render fixtures remain pending CUDA golden generation.
|
|
117
|
+
- Full suite: 88 tests, 248 assertions, no failures, 9 documented golden-data skips.
|
|
118
|
+
- RuboCop: no offenses.
|
|
119
|
+
|
|
120
|
+
### P8 — Complete (golden-data gate pending)
|
|
121
|
+
|
|
122
|
+
- Adam bias correction, exponential scheduling and synchronized structural state edits pass.
|
|
123
|
+
- Default strategy growth/pruning and MCMC relocation/growth/noise paths are covered.
|
|
124
|
+
- MCMC equation 9 is verified analytically; noise mean/variance and a reduced image fit pass.
|
|
125
|
+
- L3 strategy and relocation fixtures remain pending CUDA golden generation.
|
|
126
|
+
- Full suite: 109 tests, 323 assertions, no failures, 11 documented golden-data skips.
|
|
127
|
+
- RuboCop: no offenses.
|
|
128
|
+
|
|
129
|
+
### P9 — Complete (golden-data gate pending)
|
|
130
|
+
|
|
131
|
+
- Inria PLY and COLMAP bin/txt parsing pass round-trip and paired-fixture tests.
|
|
132
|
+
- k-NN initialization, scene scale, L1/SSIM/PSNR, checkpoint restart, and optional image backends pass.
|
|
133
|
+
- The eight-view synthetic Trainer E2E exceeds 28 dB in 80 steps; the production default remains 30,000 steps.
|
|
134
|
+
- `simple_trainer.rb` and `render_path.rb` provide the documented real-COLMAP workflow and bundled sample data.
|
|
135
|
+
- L3 SSIM remains pending golden generation; the source bundle uses chunky_png to exercise image IO.
|
|
136
|
+
- Full suite: 131 tests, 817 assertions, no failures, 14 documented skips.
|
|
137
|
+
- RuboCop: no offenses.
|
|
138
|
+
|
|
139
|
+
### P10-3 — Complete
|
|
140
|
+
|
|
141
|
+
- Native float32 intersections exactly match the Ruby keys, flattened ids, counts and offsets.
|
|
142
|
+
- The stable eight-pass radix sort preserves key/id pairing and runs without the Ruby GVL.
|
|
143
|
+
- The 1,000-Gaussian profile improves from 2.505 ms to 0.045 ms (55.7×).
|
|
144
|
+
- Full suite: 136 tests, 836 assertions, no failures, 14 documented skips.
|
|
145
|
+
- RuboCop: no offenses.
|
|
146
|
+
|
|
147
|
+
### P10 — Complete
|
|
148
|
+
|
|
149
|
+
- Contiguous float32 projection, SH, intersections, raster forward and raster backward use C kernels.
|
|
150
|
+
- Raster forward owns disjoint tiles; backward uses bounded-memory atomic scatter-add and supports
|
|
151
|
+
arbitrary channels, backgrounds, masks and absolute mean gradients.
|
|
152
|
+
- At 100k Gaussians and 800×800, 8-thread forward is 29.643 ms and combined forward/backward is
|
|
153
|
+
108.895 ms, within the 150/400 ms design targets.
|
|
154
|
+
- The explicit `:native` suite passes with one and eight OpenMP threads.
|
|
155
|
+
- Full suite: 137 tests, 845 assertions, no failures, 14 documented skips.
|
|
156
|
+
- RuboCop: no offenses.
|
|
157
|
+
|
|
158
|
+
### P11 — Complete (golden-data gate pending)
|
|
159
|
+
|
|
160
|
+
- Extended rendering covers feature chunking, direct covariance, elliptical radii, distorted/fisheye
|
|
161
|
+
cameras, distance modes, eval3d normals, 2DGS, SelectiveAdam, and contribution enumeration.
|
|
162
|
+
- Each extension has analytic/property coverage and a pinned Python generator case where upstream
|
|
163
|
+
execution is required. CUDA fixtures remain the documented external gate.
|
|
164
|
+
- Both backend selections pass the same 181-test, 977-assertion suite with 22 documented skips.
|
|
165
|
+
- RuboCop: no offenses.
|
|
166
|
+
|
|
167
|
+
### P12-1 — Complete
|
|
168
|
+
|
|
169
|
+
- README includes installation, executable quick start, conventions, API mapping, feature status,
|
|
170
|
+
limitations, and the three production examples. `docs/MIGRATION.md` covers Python migration.
|
|
171
|
+
- `test/readme_test.rb` executes the exact quick-start fence. Example smoke tests run a real image-fit
|
|
172
|
+
step, parse all COLMAP fixture records, and validate a PLY orbit setup without optional image gems.
|
|
173
|
+
- `yard stats`: 40 files, 129 methods, 124 attributes, 22 constants, undocumented 0 (100%).
|
|
174
|
+
- Ruby and native selections: 186 tests, 1,005 assertions, no failures, 22 documented skips.
|
|
175
|
+
- RuboCop: 130 files, no offenses.
|
|
176
|
+
|
|
177
|
+
### P12-2 — Complete (external validation pending)
|
|
178
|
+
|
|
179
|
+
- The 100k/800×800 workload passes all timing targets: Ruby 3,527.990/8,047.024 ms and
|
|
180
|
+
native 30.419/112.573 ms for forward/combined forward+backward.
|
|
181
|
+
- The full 50k/512²/2,000-step native image fit completes in 157.457 seconds versus the
|
|
182
|
+
30-minute target. The COLMAP benchmark path completes a one-step fixture smoke run.
|
|
183
|
+
- `docs/BENCHMARKS.md` records commands and limitations; `docs/ACCEPTANCE.md` maps G1–G6 to evidence.
|
|
184
|
+
CUDA golden parity (G1/G2) and real-capture 30k quality (G4) remain explicit external gates.
|
|
185
|
+
- CI now runs Ruby 3.2–4.0, native build/full suite, documentation, and focused E2E jobs.
|
|
186
|
+
- Ruby and native selections: 188 tests, 1,041 assertions, no failures, 22 documented skips.
|
|
187
|
+
- YARD: undocumented 0 (100%). RuboCop: 132 files, no offenses. Workflow YAML parses successfully.
|
|
188
|
+
|
|
189
|
+
### P12-3 — Complete
|
|
190
|
+
|
|
191
|
+
- Version 1.0.0, release metadata, a bounded runtime dependency, and the release file manifest are
|
|
192
|
+
covered by `test/gemspec_test.rb`.
|
|
193
|
+
- `gem build gsplat.gemspec` produces `gsplat-1.0.0.gem`. Installing that artifact into an empty
|
|
194
|
+
temporary gem home compiles and loads the native extension, then completes a 1×1 render without
|
|
195
|
+
loading files from the working tree.
|
|
196
|
+
- Ruby and native selections: 196 tests, 1,166 assertions, no failures, 22 documented skips.
|
|
197
|
+
- YARD: undocumented 0 (100%). RuboCop: 134 files, no offenses.
|
|
198
|
+
- Following explicit confirmation, the source license and gem metadata use Apache-2.0. No gem has
|
|
199
|
+
been published.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# NNNN: Decision title
|
|
2
|
+
|
|
3
|
+
- Status: Proposed
|
|
4
|
+
- Date: YYYY-MM-DD
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Describe the question, constraints, and forces that make the decision necessary.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
Describe the chosen approach and the important alternatives that were not selected.
|
|
13
|
+
|
|
14
|
+
## Consequences
|
|
15
|
+
|
|
16
|
+
Describe the positive and negative consequences, follow-up work, and conditions that could cause
|
|
17
|
+
this decision to be revisited.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 0001: Pin the golden reference and define CPU-only validation
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-23
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Golden data must remain reproducible while the upstream `main` branch and its public tensor shapes
|
|
9
|
+
continue to evolve. The development host also has no CUDA device, so it cannot execute the complete
|
|
10
|
+
upstream rasterizer locally.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- Pin Python reference data to the latest official release, `gsplat==1.5.3` (tag commit
|
|
15
|
+
`937e29912570c372bed6747a5c9bf85fed877bae`), instead of a moving `main` branch.
|
|
16
|
+
- Use the upstream 1.5.3 elliptical radii shape `[C,N,2]` in projection and golden files. The
|
|
17
|
+
low-level intersection operation continues to accept legacy scalar radii.
|
|
18
|
+
- Do not create golden `last_ids`, because upstream 1.5.3 does not expose that internal rasterizer
|
|
19
|
+
value. Raster goldens store public outputs, all public input gradients, and `means2d.absgrad`.
|
|
20
|
+
- Use the dependency-free generator dry run as the local P0-4 gate. Keep CPU partial-generation and
|
|
21
|
+
the required CUDA command documented in `tools/README.md`.
|
|
22
|
+
|
|
23
|
+
## Consequences
|
|
24
|
+
|
|
25
|
+
Golden inputs and public output contracts are reproducible against an immutable upstream version.
|
|
26
|
+
Local development can validate the generator and all CPU-owned behavior, while complete raster
|
|
27
|
+
parity remains an explicit external CUDA acceptance gate.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 0002: Retain exact Ruby fallbacks for the native backend
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-23
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The native backend must accelerate production float32 training without creating a second,
|
|
9
|
+
divergent implementation of every derivative and compatibility path. Raster backward must also
|
|
10
|
+
remain within a practical memory bound at the 100k-Gaussian benchmark scale.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- Target contiguous `Numo::SFloat` arrays in native kernels.
|
|
15
|
+
- Keep float64 gradchecks, masked spherical harmonics, and analytic projection/SH backward on the
|
|
16
|
+
Ruby implementation. Selecting `:native` remains functionally complete through these fallbacks.
|
|
17
|
+
- Label performance results for these paths as hybrid and measure accelerated operations
|
|
18
|
+
explicitly.
|
|
19
|
+
- Use OpenMP atomic scatter-add in raster backward instead of per-worker full gradient buffers.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
The native backend accelerates the high-volume path while preserving one authoritative copy of
|
|
24
|
+
complex derivative formulas. Atomic scatter-add avoids `O(workers × N × channels)` temporary
|
|
25
|
+
memory. Its measured 100k/800×800 raster backward improves from 279.790 ms with one worker to
|
|
26
|
+
79.252 ms with eight workers, at the cost of atomic-update contention.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 0003: Share the Ruby projection for distorted cameras
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-23
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Equidistant fisheye and OpenCV distortion add calibration-sensitive formulas and numerical
|
|
9
|
+
derivatives. Independent Ruby and native implementations would increase the risk of camera-model
|
|
10
|
+
drift without improving the established pinhole performance path.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- Use one float32/float64 Ruby implementation of equidistant fisheye and OpenCV
|
|
15
|
+
radial/tangential/thin-prism projection for both backend selections.
|
|
16
|
+
- Delegate these camera models to the Ruby path when `:native` is selected.
|
|
17
|
+
- Follow gsplat 1.5.3's rational pinhole coefficient order: `k1..k3` form the numerator and
|
|
18
|
+
`k4..k6` form the denominator. Fisheye coefficients multiply `theta^3..theta^9`.
|
|
19
|
+
- Treat distortion coefficients as calibration constants. Propagate gradients to Gaussian
|
|
20
|
+
geometry, but do not optimize coefficients or intrinsics in this phase.
|
|
21
|
+
|
|
22
|
+
## Consequences
|
|
23
|
+
|
|
24
|
+
Both backend selections have identical distorted-camera semantics and numerical derivatives. These
|
|
25
|
+
models run at reference speed; the optimized pinhole path and its analytic/native backward remain
|
|
26
|
+
unchanged.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 0004: Use one portable reference for world-space extension paths
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Hit-distance rendering and local depth ordering require anisotropic Gaussian evaluation in world
|
|
9
|
+
space. They extend the renderer beyond the performance-oriented 2D EWA core and introduce geometry
|
|
10
|
+
derivatives that would be costly to duplicate across backends.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- Evaluate hit-distance modes against the anisotropic Gaussian in world space and replace the final
|
|
15
|
+
feature channel with the per-pixel closest-approach distance, matching the upstream eval3d
|
|
16
|
+
definition.
|
|
17
|
+
- Use the same Ruby implementation for both backend selections and a dtype-aware central-difference
|
|
18
|
+
geometry VJP. Keep the core 2D raster path analytic and native-accelerated.
|
|
19
|
+
- Make `global_z_order: false` sort projected centers by camera-space Euclidean distance. Continue
|
|
20
|
+
near/far culling by camera z and propagate the Euclidean-norm VJP to means.
|
|
21
|
+
|
|
22
|
+
## Consequences
|
|
23
|
+
|
|
24
|
+
The extension remains differentiable and behaviorally identical under `:ruby` and `:native`
|
|
25
|
+
without maintaining two world-space evaluators. It prioritizes semantic coverage over large-scene
|
|
26
|
+
speed and leaves the established fast raster path untouched.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 0005: Reuse the differentiable EWA core for 2DGS
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The Ruby API needs a portable 2DGS-compatible surface and Trainer mode, but an exact upstream
|
|
9
|
+
ray-splat transform and median-crossing compositor cannot be validated on this CPU-only host.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
- Preserve the upstream seven-value return structure and metadata names.
|
|
14
|
+
- Reuse the established differentiable EWA footprint and compositor for color and alpha.
|
|
15
|
+
- Derive oriented camera normals, normalized surface normals, a depth-opacity distortion signal,
|
|
16
|
+
and expected depth as the median approximation.
|
|
17
|
+
- Treat exact ray-splat transforms, median crossing, and distortion accumulation as part of the
|
|
18
|
+
pending CUDA golden gate rather than claiming numerical identity.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
2DGS color/alpha rendering and Trainer optimization remain differentiable on both backends with a
|
|
23
|
+
stable public shape. Auxiliary geometry buffers are approximate and must not be presented as an
|
|
24
|
+
exact substitute for upstream 2DGS until the external parity gate can be completed.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 0006: Keep eval3d as a portable reference path
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
`with_eval3d` generalizes the world-space evaluator from
|
|
9
|
+
[ADR 0004](0004-use-portable-world-space-reference-paths.md) to arbitrary color features, and
|
|
10
|
+
`return_normals` adds view-oriented Gaussian normals. These semantics are more important than
|
|
11
|
+
duplicating them in the native raster kernels before CUDA parity is available.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
- Accumulate arbitrary features with the world-space evaluator.
|
|
16
|
+
- For normals, use the quaternion's canonical +Z axis, flip it toward the ray, and accumulate it
|
|
17
|
+
with the same alpha weights as color.
|
|
18
|
+
- Support pinhole cameras and classic rasterization.
|
|
19
|
+
- Share the Ruby evaluator and numerical geometry VJP between backend selections.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
The performance-oriented 2D raster kernels remain unchanged, while eval3d values and geometry stay
|
|
24
|
+
differentiable and portable. The analytic single-ray case validates values and quaternion
|
|
25
|
+
gradients locally; full CUDA color, alpha, and normal parity remains a generated-golden gate.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 0007: Share compositor semantics for contribution indices
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-07-24
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
`rasterize_to_indices_in_range` exposes iterative per-pixel contribution analysis. Its range
|
|
9
|
+
coordinates, ordering, and termination rules must match the regular compositor or analysis results
|
|
10
|
+
will disagree with rendered pixels.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
|
|
14
|
+
- Interpret range bounds as batches of `tile_size²` entries within each tile's sorted intersection
|
|
15
|
+
list, matching the upstream iterative rasterizer rather than indexing global intersections.
|
|
16
|
+
- Preserve image-major, pixel-major, then depth order.
|
|
17
|
+
- Apply the compositor's alpha skip, clamp, and exclusive transmittance stop.
|
|
18
|
+
- Use the portable Ruby enumerator for both backend selections and do not record an autograd graph
|
|
19
|
+
for integer analysis outputs.
|
|
20
|
+
|
|
21
|
+
## Consequences
|
|
22
|
+
|
|
23
|
+
Contribution enumeration is deterministic and agrees with regular rasterization semantics. It runs
|
|
24
|
+
at reference speed under either backend and intentionally produces non-differentiable integer
|
|
25
|
+
indices.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Example data
|
|
2
|
+
|
|
3
|
+
This directory contains a deterministic 16-Gaussian scene for the runnable examples:
|
|
4
|
+
|
|
5
|
+
- `colmap/` is a three-view COLMAP text dataset with 16×16 PNG images.
|
|
6
|
+
- `splats.ply` is the corresponding Inria-layout Gaussian model.
|
|
7
|
+
|
|
8
|
+
Regenerate both assets from the repository root:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bundle exec ruby examples/generate_sample_data.rb
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The scene is generated entirely by Gsplat and contains no third-party data.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Synthetic colored seed points.
|
|
2
|
+
1 -0.875 -0.875 2 0 51 255 0
|
|
3
|
+
2 -0.375 -0.875 2 17 60 238 0
|
|
4
|
+
3 0.125 -0.875 2 34 68 221 0
|
|
5
|
+
4 0.625 -0.875 2 51 77 204 0
|
|
6
|
+
5 -0.875 -0.375 2 68 85 187 0
|
|
7
|
+
6 -0.375 -0.375 2 85 94 170 0
|
|
8
|
+
7 0.125 -0.375 2 102 102 153 0
|
|
9
|
+
8 0.625 -0.375 2 119 111 136 0
|
|
10
|
+
9 -0.875 0.125 2 136 119 119 0
|
|
11
|
+
10 -0.375 0.125 2 153 128 102 0
|
|
12
|
+
11 0.125 0.125 2 170 136 85 0
|
|
13
|
+
12 0.625 0.125 2 187 144 68 0
|
|
14
|
+
13 -0.875 0.625 2 204 153 51 0
|
|
15
|
+
14 -0.375 0.625 2 221 161 34 0
|
|
16
|
+
15 0.125 0.625 2 238 170 17 0
|
|
17
|
+
16 0.625 0.625 2 255 178 0 0
|
|
Binary file
|