~/satyajit

3D reconstruction roundup 4: segments, affine fields and a few clicks

mdjsonmcp

2026-11-12 · 34 min · 3d · gaussian-splatting · depth-estimation · datasets · benchmarks

A 5:00 narrated explainer, drawn in code. Every number and picture in it is this article's own; the sources are below.

› transcript

Why is there a point inside every Gaussian splat? And what happens if that point becomes a line? Hi, I'm Mirabel! Five 3D reconstruction papers from this week. We'll go deep on two of them. Chapter one. A point was a choice. A three D Gaussian is a point, blurred by a smooth kernel. That makes it something a renderer can differentiate. So a splat has two knobs: the kernel, and the thing being blurred. Swap the point for a segment, or a curve, and you get these soft tubes. A Gaussian is a point blurred by a kernel. Spaghetti Splatting blurs a line segment instead, and stays in closed form. Take a segment, two endpoints. Every point along it adds a Gaussian, and together they make a soft tube. Set the endpoints equal and it is an ordinary splat. Along a camera ray, that tube integrates to two error functions. No sampling. Each segment gets an opacity per pixel, composited like ordinary splats. Cut a segment in half, and the two halves composite to exactly the parent. The image does not move, so training can split freely. Here is the formula. Find the point on the segment closest to the ray. One error function counts the part of the segment before it. The other counts the part after. A ray passing beyond an end drives one negative, and the contribution fades. A triangle would need a much harder function here. A segment needs only erf, which is why the authors stop at curves. With points, a thin strand needs a Gaussian every two kernel widths, or it beads. A segment draws it with one primitive, and bends with a simulator. The article's toy draws one line with Gaussian points. Five points, and the density ripples by seventeen percent. Six points, two kernel widths apart: two point nine percent. Seven: zero point three. A thin strand needs a point every two widths. One segment, convolved with the same kernel: one primitive, and no ripple at all. Cut it in half, slide the halves apart and back. The halves sum to the parent exactly, so the image does not move, and training can split freely. On a real room, the point version draws the bicycle spokes as scattered dots. The segments run along the spokes and edges, like pencil strokes. On rooms it ties ordinary splatting, as a superset should. On hair it wins outright, by two point seven decibels, with no hair prior. Chapter two. One scale is not enough. Splats reconstruct geometry badly, so people add a depth network as a prior. Its depth is relative, so it is aligned with one global scale and shift. That leaves forty-one percent of pixels off. Give each of three regions its own scale and shift, and eighty-four point nine percent fit. With five, ninety-two point seven. The paper's scatter shows why. Predicted against true disparity falls on three different lines, one per surface. So AffineGS estimates a scale field that follows those surfaces. AffineGS models the relation per pixel. The network gives relative depth. The scale changes from surface to surface, not one number per image. A guided filter keeps both fields smooth within a surface. Without it they would copy the render. On a benchmark of indoor rooms, meshes score point seven one, against point two eight for a global fit the authors built themselves. Chapter three. Three more, briefly. Three more papers this week, briefly. Gaussian Grids hangs a feature grid on every 2D Gaussian. Another feeds a person's matching clicks into a point-map model. And one from Meta lets a stereo pair set metric depth, under a small decoder. Here is Click3R. A person clicks the same corner of a toaster in two photos. The base model alone splits the toaster in two. With one click, it is a single object. The article checked each claim against its own tables. Spaghetti's long connected strands average three and a half segments, and the code is not out yet. AffineGS initialises from sensor depth points, and tunes its filter scene by scene. And Gaussian Grids' point one bits per pixel is about point one three, by its own file sizes. Remember: the point inside a Gaussian was a choice. A segment keeps the closed form and draws thin things in one stroke. Segments instead of points, scale and shift per surface, and grids, clicks and stereo. Thanks for watching! Every source is in the full article. I'm Mirabel. Bye!

Why read this

Solidtop 85%

Five splatting and 3D papers explained from the mechanism up, checked against their code, configs, checkpoints and data, with two live toys.

  • Original analysis
  • A new technique
  • Explained from first principles

3D & spatialRuns on a consumer GPUMixed licencesResearch roundup

How this was scored
Is it new?
2 of 3: A real new idea, method or capability
Can I trust it?
2 of 3: Measures key facts from files, code or configs
Can I run it?
1 of 3: API-only, gated or restrictive licence
Will I understand it?
2 of 3: Mechanism from first principles with figures
Can I act on it?
1 of 3: General advice
Will it last?
1 of 3: Relevant for months
Does it affect many?
1 of 3: A specialist community
Only here?
2 of 3: A teardown or measurement few others did

Score 55 of 100, ranked 558 of 740 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored

The last roundup asked how much of each release a reader could check from outside. This one starts somewhere else: what is the mechanism, in enough detail that you could implement it from the page. Four of the five papers are about Gaussian splatting. One changes what a primitive is (Spaghetti Splatting), one changes what supervises it (AffineGS), one moves the Gaussian into an image codec (Gaussian Grids), and one predicts splats from a stereo rig in a single pass (StereoSplat). The fifth, Click3R, is about the feed-forward point-map models that now sit in front of many splatting pipelines, and what to do when they get a scene wrong.

PaperWhat is newReleased on 12 NovemberWhat I checked
Spaghetti SplattingThe point inside a 3D Gaussian becomes a segment, then a polylinePaper, project page and viewer; code dated 23 NovemberThe closed form, subdivision, the margins in its table
AffineGSMonocular depth aligned by a per-pixel scale and shift fieldCode (Apache-2.0) with per-scene settingsThe headline mesh number, the training scripts, the initialisation
Gaussian GridsA feature grid attached to each 2D Gaussian, sized by local frequencyPaper; the code repository exists and is emptyThe bits-per-pixel accounting, the ablation
Click3RCross-view clicks fed into DUSt3R through a small adapterCode (MIT), weights, demo, benchmark lists and human clicksThe human clicks, the benchmark categories, the comparison with Pi3
StereoSplat and XRStereoFeed-forward splats on a frozen stereo model, and the synthetic data it trained onCode and weights (CC BY-NC), the dataset (CC BY-NC)The checkpoint header, the dataset card, three sequences read from a shard

Spaghetti Splatting: a Gaussian whose point became a line

Rasterisers draw points, lines and triangles. A learned scene would be most useful as those primitives, but each of them is a Dirac mass: no extent, so a pixel beside it gets no gradient telling it to move closer. The paper, from Andrea Tagliasacchi's group at Simon Fraser with Wayve and Toronto, starts from an idea old enough to sit inside EWA splatting and Poisson reconstruction. Convolve the Dirac with a smooth kernel and you get a density that volume rendering can differentiate. A 3D Gaussian is exactly that, a Dirac at μ\boldsymbol\mu convolved with N(0,Σ)\mathcal{N}(\mathbf 0, \boldsymbol\Sigma):

G(x)=[δ(⋅−μ)⊛NΣ](x).\mathcal G(\mathbf x) = \big[\delta(\cdot - \boldsymbol\mu) \circledast \mathcal N_{\boldsymbol\Sigma}\big](\mathbf x).

Written that way, 3DGS has two knobs. The kernel is one, and Deformable Beta Splatting already swapped the Gaussian for a compact Beta. The thing being convolved is the other. Points and triangles have been tried; this paper tries a segment.

A three by three grid. Columns: a point, a segment, and a polyline curve as Dirac masses. Rows: the same masses convolved with a Gaussian kernel, a Beta kernel and an anisotropic Beta kernel. The point with a Gaussian is labelled 3DGS and the point with a Beta is labelled DBS; the segment and polyline columns show soft tubes and capsules.
Kernel and Dirac are independent choices: a point with a Gaussian is 3DGS, a point with a Beta is Deformable Beta Splatting, and replacing the point with a segment or a polyline gives Spaghetti Splatting's primitives (Spaghetti Splatting paper, Figure 2).

From a segment to a density

A segment from a\mathbf a to b\mathbf b is the measure m(x)=∫01δ(x−a−uv) dum(\mathbf x) = \int_0^1 \delta(\mathbf x - \mathbf a - u\mathbf v)\,du with v=b−a\mathbf v = \mathbf b - \mathbf a: unit mass spread evenly along the line. Convolve it with the same Gaussian and scale by a density ρ\rho:

σ(x)=ρ∫01NΣ(x−a−uv) du.\sigma(\mathbf x) = \rho \int_0^1 \mathcal N_{\boldsymbol\Sigma}\big(\mathbf x - \mathbf a - u\mathbf v\big)\,du .

Every point of the segment contributes a Gaussian and the density is their sum. Set a=b\mathbf a = \mathbf b and the integral collapses to one Gaussian, so a 3DGS scene is a Spaghetti scene whose segments all have zero length. The "strict superset" claim is true by construction.

The ray integral is an error function

Volume rendering needs the optical depth τ\tau along each ray r(t)=o+td\mathbf r(t) = \mathbf o + t\mathbf d. The density is already an integral over uu, so τ\tau is a double integral, and because the Gaussian is non-negative the order can be swapped. A 3D Gaussian integrated along a line is a 1D Gaussian in uu, with a centre uˉ\bar u (the point of the segment closest to the ray, in the kernel's metric) and a precision κ2\kappa^2 that says how quickly the segment leaves the ray. A Gaussian integrated over u∈[0,1]u \in [0, 1] is a difference of error functions:

τ(r)=ρ f(uˉ)π2κ[erf⁡(κuˉ)+erf⁡(κ(1−uˉ))],α=1−e−τ.\tau(\mathbf r) = \frac{\rho\, f(\bar u)\sqrt\pi}{2\kappa}\Big[\operatorname{erf}(\kappa\bar u) + \operatorname{erf}\big(\kappa(1 - \bar u)\big)\Big], \qquad \alpha = 1 - e^{-\tau}.

f(uˉ)f(\bar u) is the kernel's line integral at the closest point. A ray far from the segment makes it tiny. A ray grazing the middle puts both error functions near one. A ray passing beyond an end drives one of them negative and the contribution fades. A triangle convolved with a Gaussian would need Owen's T function at this step; a segment needs only erf, which the authors give as the practical reason to stop at curves.

Evaluating that per primitive and per pixel is too slow for training, so the paper does what EWA splatting does for points. It linearises the camera projection once per frame at the segment's midpoint and scales the projected kernel along the segment by inverse depth, so the near end of a receding segment is drawn wider than the far end. Per-pixel work drops to a few screen-space quantities, and the Beta kernel fits too, since nothing now depends on the kernel being Gaussian in 3D. Mip-Splatting's anti-aliasing filters add to the projected covariance along the whole segment and come along for free.

Why a thin strand wants a segment

A strand of width σ\sigma drawn with points needs its points close together or it beads. The widget draws a line ten units long both ways in the image plane, using the paper's formula: one segment, or the same total mass as KK Gaussian points.

At σ=1\sigma = 1 the centre line of five points ripples by 17% of its mean, six points (spacing 2σ2\sigma) by 2.9%, seven by 0.3%. Those are my toy's numbers, and they say a line of length LL needs about L/2σ+1L / 2\sigma + 1 Gaussians before it stops looking like a string of beads. A spoke one pixel wide wants a Gaussian every two pixels of its length. The segment needs one primitive and has no ripple. The paper's Figure 3 shows the same thing on a real scene: the 3DGS-MCMC centres on the bicycle spokes are scattered dots, and the Spaghetti segments run along the spokes.

Three panels of the Mip-NeRF 360 bonsai room. Left, the 3DGS-MCMC primitives as a cloud of black dots. Middle, the photorealistic render, a bonsai on a table in front of a piano and a bicycle. Right, the Spaghetti Splatting primitives drawn as thin strokes that follow the bicycle wheel, the piano stand and the table edges.
The same render from two representations: 3DGS-MCMC's point centres on the left, Spaghetti Splatting's segments on the right, which follow the spokes and edges as strokes (Spaghetti Splatting paper, Figure 3).

Subdivision is exact, and this matters more for training than it looks. The convolution is linear, so cutting a segment at its midpoint into two halves that keep its density per unit length splits τ\tau into two terms that sum to the original. Compositing the halves gives (1−α1)(1−α2)=e−τ1−τ2=1−α(1 - \alpha_1)(1 - \alpha_2) = e^{-\tau_1 - \tau_2} = 1 - \alpha, the parent's transmittance, and the image does not move. The widget's last readout checks the density half of that: the two halves differ from the parent by about 10−1610^{-16} of its peak, which is rounding. Training keeps 3DGS-MCMC's relocation, splits long segments without a jump in the loss, clones short ones, and merges polylines whose endpoints come close, helped by a loss that pulls nearby endpoints together. The project page adds that each kernel's local x axis is its segment's direction, so when a simulator bends a strand, the kernels turn with it.

The subdivision argument as the authors animate it: cut a segment anywhere, put the halves back, and the density profile is the one you started with (video from @taiyasaki's thread on X).

A tie on rooms, a clear win on hair

On Mip-NeRF 360 the paper's table has the Beta-kernel version at 28.78 dB PSNR, against 28.71 for DBS with the same Spherical Voronoi appearance model and 28.39 for 3DGS-MCMC. The Gaussian-kernel version leads on Shelly with 37.38 against 37.22 and on Fuzzy with 35.03 against 35.00. Those margins are 0.07, 0.16 and 0.03 dB: a tie on general scenes, which is what a superset of 3DGS ought to deliver. The concurrent Line Primitives method of Tojo et al. scores 8.66 dB on Mip-NeRF 360 because its lines keep a constant screen width and cannot represent a room.

Frame rate is close without being equal. On an RTX 4090 the paper measures 177 frames per second against 198 for 3DGS-MCMC on Mip-NeRF 360, 452 against 464 on Shelly and 415 against 422 on Fuzzy, between 89% and 98% of the point version.

Hair is where the primitive pays. On eight GeomHair hairstyles plus Cem Yuksel's curly model, rendered at 1200×800 with 88 training and 12 test views, it beats Gaussian Haircut by 2.7 dB (32.25 against 29.56) and by 14.3 points of strand F1 at 2 mm (61.2 against 46.9), with no hair prior, and the strands go straight into position-based dynamics.

One hairstyle reconstructed by Spaghetti Splatting, turning: the render on the left, the recovered strands on the right. A single scene the authors chose for the page (project page).

One row of the ablation table deserves more attention than the paper gives it. The mean connected component is 3.50 segments for the full method and 1.00 with merging off, at PSNR 33.54 against 33.59. Merging costs nothing in image quality, but the "long connected strands" of the abstract average three and a half segments on these benchmarks. The conclusion is candid about it: "broader claims about automatically recovered topology require further evaluation".

Nothing runs yet. There is a project page with a web viewer of three scenes, the pasta among them, and a repository whose only file is a README reading "Code coming on November 23rd, 2026"; that was the state when I cloned it on 12 November.

13 November: a honeybee you can blow on. The project page's Viewer menu has a fourth entry, dated 12 November in the page source, after the two room models and the pasta: a honeybee, in a separate WebGPU viewer. @bettercallshaz posted a recording of it the next morning, crediting @taiyasaki, Dany Bittel and Will Eastcott. The page thanks Dany Bittel "for providing the bumblebee capture data, and Will Eastcott for showing a 3DGS reconstruction of it to the world" (the scene is named Honeybee). Eastcott's credit is for that earlier 3DGS version; this viewer is the authors' own WebGPU code, and none of its script modules mentions PlayCanvas.

Fourteen of the post's 21 seconds in the honeybee viewer: the strands on the left, the trained render on the right. The 12 fps in its stats line is the poster's machine, not a benchmark (video from @bettercallshaz's post on X).

The model is on Hugging Face as shaygeko/spaghetti-splatting-viewer. Its header.json lists 500,000 segments, a run named honeybee_xl_render_full_attr10_500k_turn35_30k_seed0_local_v1 and a recorded_eval_psnr of 35.81 dB, on an evaluation split it does not describe. The scene's files come to 119.4 MB, the wind simulation's data included. The viewer has a Render, a Primitives and a Split view and a "Wind on the fur (live simulation)" toggle. That is the paper's point that the strands go straight into a simulator, made something you can watch move. It is also one scene the authors chose, with no metric beyond that PSNR, and the stats line in the clip (12 fps at 768×388 on an NVIDIA Turing card in a browser) says nothing about the paper's RTX 4090 numbers. Nothing else has moved: on 13 November the repository still holds only its README, last committed on 9 November, and the project page still shows "ETA: Nov 23" under Code.

AffineGS: one scale and shift is not enough

3DGS renders well and reconstructs geometry badly: layered, floating, overfit shells. The cheap fix is a monocular depth network as a prior. Depth Anything V2 and its peers predict relative depth, so the prior has to be aligned first, and the usual move (DRGS, and the monocular variant of DN-Splatter) is one global scale and shift solved by least squares. AffineGS, from dConstruct Robotics and NUS, accepted at NeurIPS 2026, measures how wrong that is. In its Table 1, on MuSHRoom, one affine transform leaves 41% of pixels outside a 0.02 residual. Split each image into three regions with their own transforms and 84.9% fit; with five, 92.7%. ScanNet++ behaves the same, 58.6% then 84.6% then 91.8%, with RMSE falling from 0.088 to 0.039.

Top row: a reference photo of an office corridor, Splatfacto's render with a noisy shattered mesh, and AffineGS's render with a clean planar mesh. Bottom row: ground-truth disparity, monocular disparity, a segmentation of the image into three affine regions, a scatter plot of monocular against true disparity with three straight lines of slopes 1.01, 2.52 and 1.93, and the smoothed scale field alpha.
The paper's case in one figure: monocular disparity against the truth falls on three different lines, one per region (scatter), so one global scale and shift cannot fit it; AffineGS's smoothed scale field (right) follows those regions (AffineGS paper, Figure 1).

Scale from a ratio of gradients

Let d(u,v)d(u,v) be the monocular disparity and D(u,v)\mathcal D(u,v) the depth 3DGS currently renders. AffineGS models the relation per pixel:

1d=α(u,v) D+β(u,v).\frac{1}{d} = \alpha(u,v)\,\mathcal D + \beta(u,v).

Differentiate with respect to image position, assume the fields vary more slowly than depth (∇α≈0\nabla\alpha \approx 0, ∇β≈0\nabla\beta \approx 0), and the shift drops out:

−1d2∇d≈α ∇D⇒α=∥∇d∥d2∥∇D∥+ϵ,β=1d−α D.-\frac{1}{d^2}\nabla d \approx \alpha\,\nabla\mathcal D \quad\Rightarrow\quad \alpha = \frac{\lVert\nabla d\rVert}{d^2\lVert\nabla\mathcal D\rVert + \epsilon}, \qquad \beta = \frac1d - \alpha\,\mathcal D .

The local scale is a ratio of gradient magnitudes, clamped to its 5th and 95th percentiles, and the shift follows from the model. A guided filter then smooths both fields, with the disparity image as its guide and a radius of 550 to 650 pixels on MuSHRoom, so they stay flat inside a surface and break at the edges dd already has. The smoothed fields give a corrected depth D^=(1−βsd)/(αsd)\hat{\mathcal D} = (1 - \beta_s d)/(\alpha_s d), which is fitted once more with a global aD^+ba\hat{\mathcal D} + b to the render so the scene cannot drift. It supervises through three losses. A gradient loss matches −αsd2∇D-\alpha_s d^2 \nabla\mathcal D to ∇d\nabla d in magnitude and direction. A second copy of it computes ∇D\nabla\mathcal D from the rendered normals and the intrinsics, which constrains the Gaussians' orientations and not only their depths. An L1 depth loss anchors the scale. Weights 0.1, 0.08 and 0.4, and every field is re-estimated each iteration.

Without the filter, the prior says nothing

Before smoothing, the fields are computed from the render, so they reproduce it. Substitute β=1/d−αD\beta = 1/d - \alpha\mathcal D into the corrected depth and D\mathcal D comes back unchanged; that is two lines of algebra on the paper's Eq. 1 and Eq. 9. A per-pixel affine field can fit anything. The prior carries information only because the guided filter forces the fields to be nearly constant across a surface, so the target takes its shape from dd within each surface and its scale from the render averaged over it. The filter radius is the real hyperparameter.

The widget is a one-row version of that argument, written for this article, not taken from the authors' code. Three surfaces, a monocular prediction that is exactly affine within each surface with different coefficients per surface, and a render that is right on average apart from a dent, a bump and blotchy noise.

With the surfaces disagreeing fully, one scale and shift fitted to the true depth leaves a mean error of 7.9 cm, with 46% of pixels within 5 cm. One fit per surface leaves none, since the toy's distortion is exactly piecewise affine; that is the paper's Table 1 in miniature. The estimator, which only sees the render, gives 4.6 cm against the render's own 5.5 cm at 5 cm of render noise, so the target pulls the render the right way. Give it a clean render and it lands at 3.8 cm against 3.3: nothing to add, as the algebra predicts. In one dimension a wide window hurts, 7.8 cm at a radius of 120 of 360 pixels, because the filter averages across surfaces. I could not reproduce the benefit of the paper's very large window in 1D, and I can't tell from here whether that is the toy's limit or a property of images, where a big 2D window still covers mostly one surface.

The 0.71 holds, with three asterisks

The X thread that circulated the paper quoted a MuSHRoom mesh F-score of 0.71 against 0.28 for DN-Splatter with monocular depth. Table 4 prints 0.7097 against 0.2765, so the quote is accurate. It is less than it sounds:

Normals from rendered depth in the MuSHRoom coffee room: DN-Splatter with one global scale and shift in the middle, AffineGS on the right. The DN-Splatter here is the monocular baseline the authors built, and the on-screen line about no sensor-depth supervision refers to the losses, not the initial point cloud (video from @Elden00265397's post on X).

On ScanNet++, two scenes, AffineGS has the best normal consistency of the sensor-free methods (0.8308) and a lower F-score than 2DGS and SuGaR, 0.3207 against 0.4196 and 0.4367; the paper says so and blames a 5% depth shortfall on one scene. The sparse-view LLFF results, from a separate DRGS-based code base, are its strongest: mean PSNR 18.79 against DRGS's 17.31 at two views and 23.32 against 20.78 at five. Depth Abs Rel on MuSHRoom is 0.0551, against 0.3542 for the monocular DN-Splatter and 0.0228 for DN-Splatter with real sensor depth, so most of the gap to the sensor closes and some does not.

The code is out under Apache-2.0, built on DN-Splatter and Nerfstudio, last commit 6 November, with one script per table and a warning that the Depth Anything V2 Large weights it needs are CC BY-NC.

eldentse/AffineGS@0989802 · snapshot 2026-11-12

[NeurIPS 2026] AffineGS: Unlocking Accurate Geometry in 3DGS via Spatially Varying Affine Rectification

stars
22
license
Apache-2.0
branch
main
forks
4
open issues
0
source
824.9 kB
commit date
2026-11-06
source by language
Python792.3 kBShell32.7 kB

GitHub REST API, 2026-11-12 — branch, commit, commitDate, createdAt, description, forks, languages, license, openIssues, pushedAt, stars

Gaussian Grids: a feature grid on every Gaussian

This one is about images, not scenes. Neural image codecs come in two families. Feature-grid methods (Instant-NGP, Neural Texture Compression) store a regular grid of learned features and decode with a small MLP: compact and fast, but they spend as much on sky as on a fence. Primitive methods (GaussianImage, Image-GS) put 2D Gaussians where the detail is, but each Gaussian carries its own position, shape and colour, and fine texture needs a great many of them. Gaussian-Grid, from NYU and Intel for WACV 2027, lets the Gaussians decide where capacity goes and stores the capacity as a small feature grid attached to each one. The summary that circulated had it as grids for flat regions and Gaussians for the rest. It is closer to the reverse: every Gaussian carries a grid, and one low-resolution "canvas" grid covers the smooth background.

Five panels of a photo of star trails over a lifeguard tower: the target image; its local frequency map in grey; the frequency map re-rendered from optimised 2D Gaussians; a crop showing rainbow-coloured feature grids laid along the star trails; and the reconstructed image.
Gaussian-Grid's two stages: 2D Gaussians are fitted to a local frequency map, then each carries a feature grid sized to its frequency, and a small MLP decodes the result (Gaussian Grids paper, Figure 2).

Fit the frequency map first, the image second

For every pixel, take the 8×8 window around it, compute its 2D DFT and keep the largest magnitude that is not the DC term:

F(i,j)=max⁡u≠0∣F[I(i,j)](u)∣.F(i,j) = \max_{\mathbf u \neq 0}\big|\mathcal F[I_{(i,j)}](\mathbf u)\big| .

The map is high on texture and edges and near zero on flat colour. Stage one fits that map, not the image, with anisotropic 2D Gaussians that each carry a scalar frequency fnf_n, under an L1 loss plus a pairwise overlap penalty that makes them tile the image instead of piling up: 3,000 steps in Taichi, starting from 800 to 2,300 Gaussians depending on the budget.

Stage two gives each Gaussian a share of the bit budget proportional to its frequency and its footprint,

wn=∣fn∣max⁡j∣fj∣⋅4σx,n⋅4σy,n,w_n = \frac{|f_n|}{\max_j |f_j|}\cdot 4\sigma_{x,n}\cdot 4\sigma_{y,n},

raised where the local frequency vectors point many ways and lowered where they agree, with the grid's height and width following the Gaussian's anisotropy. The canvas takes 30% of the budget by default, more for flat images. A pixel samples the top K=5K = 5 Gaussian grids covering it, bilinearly and in each Gaussian's own frame, averages the valid samples, adds the canvas sample and decodes the 6-dimensional feature with a two-layer MLP of width 64. Grids train at 4 bits with quantisation-aware training, the MLP at 8, the Gaussians frozen to fp16, for 75,000 steps. The paper puts that at 24.3 minutes per image on an A100, which is the price of the format.

Better PSNR, worse LPIPS than a plain grid

On 100 DIV2K validation images it wins clearly at the lowest budget, 25.58 dB at 0.1 bits per pixel against 24.72 for the next method, and ties or edges ahead higher up: 29.36 against 29.36 at 0.4, 32.55 against 32.43 at 1.0. The ablation is less tidy. A single uniform grid given the whole budget reaches 32.08 dB at 1.0 bpp to the full method's 32.55, but the uniform grid wins on SSIM (0.9069 against 0.8999), MS-SSIM and LPIPS (0.0993 against 0.1167). The paper explains the trade plainly: capacity moves from flat regions to textured ones, PSNR goes up and the perceptual scores in smooth areas go down.

The bits-per-pixel accounting is where I would push back. The application section compares against JPEG plus ZIP on the same 100 images and lists dataset sizes. At "0.1 bpp" the 100 encoded images take 4.62 MB, 46.2 KB each, and the paper gives a decoded image as 8,306 KB, about 2.77 million RGB pixels. Divide one by the other and the nominal 0.1 is 0.13 bits per pixel; the other budgets come out near 0.43, 0.73 and 1.03. The excess is an almost constant 10 to 12 KB per image, which looks like the Gaussians and the decoder left out of the nominal budget, though the paper does not say. The JPEG comparison is size-matched, so it stands: 2.33 dB better than JPEG at the same size, decoded in 5.44 ms against 11.65 on an RTX 5080, and 47 KB over PCIe instead of 8,306 KB, the abstract's 176×. At 1.0 bpp JPEG is better by 1.00 dB. Whether the primitive baselines' 0.1 bpp counts the same bytes is the open question.

The paper says the source "is released at" a GitHub repository. The GitHub API shows that repository was created on 21 October with a size of zero, and cloning it returns an empty repository.

Click3R: a few clicks into DUSt3R

DUSt3R and its successors predict a point map for two images in one forward pass. When they get it wrong (repeated texture, a symmetric building, a large viewpoint change) the only options are to accept the output or fall back to optimisation. Click3R, from the University of Virginia, adds an input: a few correspondences a person clicks, one point in each image per click, the way SAM takes clicks for segmentation.

Architecture diagram. Two images of a domed ceiling with coloured star-shaped clicks pass through a shared ViT encoder. The clicks go through a tokenizer into a correspondence adapter per view, built from self-attention, cross-attention and an MLP, whose output is added to the image tokens before each view's transformer decoder and head, producing a 3D reconstruction.
Click3R: clicks become tokens, a two-way attention adapter per view folds them into DUSt3R's decoder input, and the rest of DUSt3R is unchanged (Click3R paper, Figure 2).

How a click becomes a token

DUSt3R encodes each image with a shared ViT-Large (24 blocks, width 1024), projects the patch tokens to width 768 and runs two 12-block decoders that cross-attend to each other. Click3R sits between the projection and the first decoder block. Each clicked point becomes a token made of three parts: its coordinates, normalised to the token grid and encoded with SAM's random Fourier features; a learned embedding for which view it is in; and a learned embedding for which click it is, shared by both ends. The pair embedding is what tells the network that this point in image one is that point in image two. Per view, a two-layer adapter runs self-attention over the clicks, cross-attention from clicks to image tokens, an MLP, and cross-attention from image tokens back to clicks, and its output updates the image tokens. The image positional encoding uses the same Fourier features, so a click and the patch under it share one coordinate space. With no clicks the adapter is the identity.

The adapter is small; the training is not. Everything is trained end to end from CroCo v2 with simulated clicks, a random number per sample, and freezing DUSt3R to train only the adapter is the worst of the three variants the paper tried, 71.2 mAA@30 against 75.1. The README lists the checkpoint at 566.6M parameters against 532.3M for DUSt3R retrained on the same data, so the clicks cost about 34M parameters. Inputs are 224×224.

The toaster example from the project page's demo recording: one click pair, and the two red dots that DUSt3R puts far apart land together in Click3R's reconstruction. It is the authors' first example, picked to show the effect (project page).

Hard3DBench is mostly wide baselines

The authors built Hard3DBench to make DUSt3R fail: 404 in-distribution and 300 out-of-distribution pairs, plus 5-view tuples. The category file they released shows what "hard" mostly means. In categories.json, 371 of the 404 in-distribution pairs and 235 of the 300 out-of-distribution pairs are tagged large baseline, many of them large viewpoint as well. Symmetric and textureless scenes, named in the abstract, are not categories at all. On the out-of-distribution pairs the retrained DUSt3R scores 52.6 mAA@30; one click gives 56.6, four give 60.4, eight give 62.4.

Pi3 at the same 224 resolution, with no clicks, scores 71.1 on those pairs, and VGGT at its native 518 scores 81.3. The paper lists them as references rather than baselines because they are larger and trained on more data, which is fair. The practical reading is still that clicks fix DUSt3R, not that they beat a stronger model. The standard-benchmark gains that look largest also come from oracle click selection. "1p_best5" picks the best of five random clicks by reconstruction loss; the "grid oracle" tries one click in each of about 256 cells and keeps whichever most reduces the pose error. That is where 84.27 to 92.75 on ARKitScenes comes from. It measures headroom, not what a person achieves.

What one person's clicks did, and on what

The human study is the part that measures a person. One annotator, 100 out-of-distribution pairs, no access to depth or poses: median rotation error falls from 17.30° to 8.78°, mAA@30 rises from 36.2 to 56.2, and four random ground-truth clicks reach 8.40°. The paper's "96% of the gap" is (17.30−8.78)/(17.30−8.40)=95.7%(17.30 - 8.78)/(17.30 - 8.40) = 95.7\%, so that checks.

I read the released human_clicks.json. It holds 377 click pairs over the 100 image pairs, a mean of 3.77 and a range of 1 to 7, which matches the paper's 3.8. It also shows that 77 of the 100 pairs come from DTU, 12 from ETH3D, 4 each from NRGBD and 7-Scenes and 3 from ScanNet. DTU is tabletop objects shot from a robot arm, so the human result is mostly a result about objects. The perturbation analysis adds a practical limit: predictions start following a wrong click once it is off by half of a 16-pixel patch, 8 pixels.

Of the five, this is the release with the least missing: training and evaluation code (MIT), weights (CC BY-NC-SA 4.0), a Gradio demo on Hugging Face Spaces, and Hard3DBench's sample lists, labels, clicks and unrounded paper numbers (CC BY 4.0). The benchmark does not redistribute images; the code prepares them from the source datasets.

UVA-Computer-Vision-Lab/click3r@b245b6a · snapshot 2026-11-12

[NeurIPS 2026] Click3R: Interactive Stereo 3D Reconstruction with Sparse Correspondence Clicks

stars
11
license
MIT
branch
main
forks
0
open issues
0
source
576.8 kB
commit date
2026-11-10
source by language
Python571.7 kBJavaScript5.1 kB
3dreconstruction

GitHub REST API, 2026-11-12 — branch, commit, commitDate, createdAt, description, forks, homepage, languages, license, openIssues, pushedAt, stars, topics

StereoSplat and XRStereo: let the rig set the scale

Feed-forward splatting models (pixelSplat, MVSplat, DepthSplat, AnySplat) take a few images and emit Gaussians in one pass. Their geometry is up to scale, or comes from cost volumes that blur with distance. A headset or a robot usually has something better: a calibrated stereo pair, from which metric depth is fb/df b / d for focal length ff, baseline bb and disparity dd. StereoSplat, from Meta and the University of Amsterdam, builds the splatting model around that.

Pipeline diagram. Left: stereo frames enter a grounding module that outputs multi-scale features and depth maps, which a Gaussian decoder turns into Gaussians, which a learnable aggregation module merges into a compact set. Right: the grounding module runs FoundationStereo on the left-right pair and on the flipped right-left pair, producing two disparity maps that become two depth maps using the focal length and baseline, plus multi-scale features.
StereoSplat: a frozen FoundationStereo gives disparity for both views (the right one by flipping the pair) and a feature pyramid; a decoder predicts per-pixel Gaussians; aggregation merges them in inverse-depth space (StereoSplat paper, Figure 2).

Four stages on top of a frozen stereo model

  1. Grounding. A frozen FoundationStereo computes the left view's disparity. For the right view, flip both images and swap them so the right image plays "left", then flip the output back. Depth for both views is fb/df b / d, and the same pass yields a four-level feature pyramid.
  2. Cross-view fusion. At every pyramid level and pixel position, the features of all 2N2N views form a sequence of length 2N2N for a small transformer. Attention is 2N×2N2N \times 2N per pixel whatever the image size, and zero-initialised output projections make it start as the identity.
  3. A gated depth. The decoder upsamples to full resolution, predicts Gaussian attributes per pixel and a refined depth D^\hat D, and blends it with the stereo depth by one learned scalar, D′=(1−α)D+αD^D' = (1 - \alpha) D + \alpha \hat D, initialised to trust the stereo.
  4. Merging in inverse depth. The 2NHW2NHW Gaussians overlap heavily across frames. Each centre maps to (x/z, y/z, 1/z)(x/z,\, y/z,\, 1/z) and is hashed on a grid of size δ\delta, so a cell is about δz\delta z wide and δz2\delta z^2 deep: fine nearby, coarse far away. Quantised colour, scale and opacity join the hash key so a cell never merges across an edge, and Gaussians in a cell are averaged with predicted softmax weights that start uniform.

Training renders the merged Gaussians into held-out views with gsplat and supervises colour (L1, SSIM, LPIPS) and metric depth (L1), on synthetic data only: XRStereo, TartanAir and Infinigen.

The trained part is 5.1 million parameters

I read the header of stereosplat_v1.safetensors on Hugging Face with two range requests. It holds 317 tensors and 5,143,087 parameters, all float32, about a third of them in the three cross-view fusion blocks. The config stored in the header sets spherical-harmonic degree 0, so colour is not view dependent, and a maximum depth of 80. What StereoSplat trains is a small head; the geometry comes from FoundationStereo, which the README has you clone separately under NVIDIA's own licence.

On KITTI, outside its training data, the paper reports novel-view depth Abs Rel of 0.04 against DepthSplat's 0.13, δ1 of 96.0 against 79.8, and PSNR of 25.2 against 22.3 for pixelSplat. On Infinigen, Abs Rel is 0.03 against 0.21. Read the protocol before reading those as a contest. The baselines run their public checkpoints, not retrained on stereo; MVSplat, pixelSplat and VicaSplat run at their native 256×256 against StereoSplat's 320×640; and the pose-free ones work in a normalised space where metric depth means nothing. The pose-aware baselines do get ground-truth poses while StereoSplat uses MAC-VO odometry. The geometry gap is mostly the rig's information, which is the paper's thesis, and it says little about whether the decoder is better at what the baselines attempt. The aggregation's compression rate in the ablation is a modest 1.6.

StereoSplat on a short sequence: input, rendered novel views, predicted depth and rendered depth. The post announcing XRStereo carried it but does not say which dataset the scene is from, and it is not one of the floating-object rooms I read from the XRStereo shard (video from @HuggingPapers's post on X).

XRStereo: what the paper says and what was uploaded

The paper describes 137k frames in 20k sequences across 9 indoor and 4 outdoor environments, objects drawn from 200 assets, rendered at 1K with horizontal fields of view from 50 to 130 degrees and baselines from 40 to 120 mm. The Hugging Face card and its manifest.json describe the upload: 17,922 sequences of 5 stereo frames, 89,610 stereo frames and 179,220 images at 1024×1024, 372.8 GB in 72 tar shards, CC BY-NC 4.0, and "This release is the StereoSplat training split". Nothing published reconciles the paper's 137k frames with the upload's 89,610.

I pulled the first 40 MB of shard 0 with a range request, which holds three sequences. Their focal lengths give horizontal fields of view of exactly 70, 60 and 80 degrees, their baselines are 120, 60 and 40 mm, and the camera moves 4.7, 11 and 8 cm between frames. Left and right disparities agree within one pixel on 91% to 100% of pixels, the rest being occlusion. The first sequence is a blank wall about 0.6 m away. The scenes are rooms full of floating, randomly posed objects, closer to FlyingThings than to a home.

Three square panels: a left and a right rendered image of a kitchen filled with floating, colourful objects such as a ring, a fan and patterned blocks, and the left disparity map in a dark purple to bright orange colour scale, near objects brightest.
One XRStereo frame (sequence 2 of shard 0): left image, right image, and left disparity, which I read from the dataset with a range request and rendered myself. 80 degree field of view, 40 mm baseline.

Everything here is non-commercial. The code (facebookresearch/stereo-splat, 89 files, last commit 11 November), the weights and the dataset are all CC BY-NC 4.0, while the project page still says code is coming soon.

facebookresearch/stereo-splat@c05eaa5 · snapshot 2026-11-12

Source code for the NeurIPS 2026 paper StereoSplat Metric-Scale Novel View Synthesis via Stereo-Grounded Gaussian Splatting

stars
2
license
custom
branch
main
forks
0
open issues
0
source
395.4 kB
commit date
2026-11-11
source by language
Python392.4 kBShell3.0 kB

GitHub REST API, 2026-11-12 — branch, commit, commitDate, createdAt, description, forks, languages, license, openIssues, pushedAt, stars

Where I'd spend time

If you build splatting pipelines, read Spaghetti Splatting and AffineGS properly; skim the rest.

Spaghetti Splatting is the idea I expect to outlast its paper. The point inside a Gaussian was a choice, and a segment keeps everything that made the point convenient: a closed form, the EWA linearisation, anti-aliasing, and a subdivision that does not change the image. On rooms it ties 3DGS, as a superset should. On hair, spokes and wires it wins outright. The connected strands are shorter than the abstract implies, and there is no code until 23 November, so hold judgement on the simulation story until you can run it.

AffineGS makes a point I agree with: a single global scale and shift throws away most of what a monocular prior knows. The method holds up, with three asterisks on its headline number: a baseline the authors built, a sensor point cloud at initialisation, and a filter tuned scene by scene. If you already regularise splats with Depth Anything, the per-surface alignment is the part to steal; the code is Apache-2.0.

Gaussian Grids is a careful hybrid, but I don't buy the 0.1 bpp headline until the Gaussians and the decoder are counted, and there is no code to check it against. Click3R is the most complete release of the week and the easiest to try in the browser. Its own data says the human study is mostly DTU objects and that Pi3 without clicks still leads on its hard pairs, so I would put clicks on Pi3 before I put them on DUSt3R. StereoSplat's geometry numbers come from information the baselines never had. What I would actually download is XRStereo, if non-commercial use is enough for you.

For where these primitives end up, see SplatAnything, InfiniSplat v2, the capture side in Gaussian-splat capture in practice, and how splat colour is stored in Gaussian-splat colour.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "3D reconstruction roundup 4: segments, affine fields and a few clicks", ai.satyajit.si, November 2026.

bibtex
@misc{ghana20263dreconstructionroundup4,
  author = {Satyajit Ghana},
  title  = {3D reconstruction roundup 4: segments, affine fields and a few clicks},
  url    = {https://ai.satyajit.si/articles/3d-reconstruction-roundup-4},
  year   = {2026}
}
share