GARfVDB - #308
Conversation
…rtifacts Integrate GARfVDB scale-conditioned instance segmentation as a first-class fvdb-reality-capture method, treating the Gaussian reconstruction as an input carrier and the learned scale-conditioned feature field as the product. This ports the model, loss, optimizer, datasets, SAM2 preprocessing, trainer, writer, and visualization out of the fvdb-examples prototype and into fvdb_reality_capture.instance_segmentation, and adds the shared infrastructure needed to support learned products that are not Gaussian splats. Add a portable, pickle-free .garfvdb bundle (manifest.json, encoder.nvdb, network.safetensors, carrier.ply). The encoder grid topology and per-voxel features round-trip through fVDB NanoVDB I/O, dense network tensors go through safetensors, and the exact filtered Gaussian carrier is stored as PLY. Manifests carry an explicit integer schema_version and per-payload checksums, and loading dispatches through a version-specific reader that validates canonical grid names/order, topology, transforms, feature shape/dtype, carrier count, and checksums. Add a method-neutral, versioned training-checkpoint envelope (schema, schema_version, method, method_version, state) with a schema-version reader registry and a legacy-adapter hook. Method identity and legacy recognition live in their owning packages (radiance_fields.checkpoint, instance_segmentation.checkpoint) rather than in the generic module, and each method exposes a single version constant that both its state and its writer envelope derive from so the two cannot drift. frgs resume now loads one validated envelope, resolves a stable method id, and invokes a CLI-owned resume handler; unknown methods fail explicitly instead of falling through to Gaussian reconstruction, and portable .garfvdb products are rejected as non-resumable. Integrate GARfVDB preprocessing with the standard scene-transform pipeline via a shared SceneTransformConfig, and represent SAM2 mask/scale supervision as a registered, namespaced GARfVDBMaskAttribute on SfmScene instead of overwriting the scene cache. Mask generation is terminal in the pipeline and refuses later image/crop/spatial transforms, so other segmentation methods can attach their own attributes and generators without inheriting the GARfVDB supervision format. Add frgs instance-segmentation, product-aware frgs show, and dispatched frgs resume; add safetensors and require fvdb-core>=0.5.0. Add API, CLI, artifact, scene-transform, and checkpoint tests, plus workflow and API documentation. Keep cuML clustering and NVOS evaluation out of the core dependency set. Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…ttributes cache Switch batch size default to 1 Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Add GARfVDBOverlayViewer, which renders the GARfVDB feature field as an
overlay on top of the Gaussian carrier in the fvdb viewer with interactive
"Scene Params" widgets:
- Grouping scale (normalized 0..1, mapped to the model's max grouping scale)
- Overlay opacity
- Show/hide the segmentation overlay (removes the image view so the carrier
shows through, since ImageView has no visibility toggle)
- Lock PCA colors: freeze the PCA->RGB transform (mean/basis/min-max) so the
feature coloring does not flicker as the camera moves; refit on unlock or
when the grouping scale changes
The overlay is driven by a shared core reused by both the offline `frgs show`
viewer and a new live training viewer (`frgs instance-segmentation --viewer`),
which pumps render_once() from the training loop between steps. render_once()
now reports whether anything changed so the offline loop renders continuously
while the camera moves and only idle-sleeps when nothing changed, tracking
camera motion in real time instead of waiting for the mouse to stop.
Add fit_pca_projection/apply_pca_projection helpers for the lockable PCA
transform and unit tests covering widget registration, normalized->raw scale
mapping, change detection, show/hide, lock behavior, and the render/idle
contract.
Default the compute device to "cuda:0" across the CLI, foundation models,
config, and docs: the rebuilt fvdb/nanovdb-editor requires an explicit device
index ("cuda" alone raised "Device must specify an index").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
harrism
left a comment
There was a problem hiding this comment.
Submitting a partial review. Haven't reviewed the big files yet.
Co-authored-by: Mark Harris <mharris@nvidia.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-authored-by: Mark Harris <mharris@nvidia.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-authored-by: Mark Harris <mharris@nvidia.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-authored-by: Mark Harris <mharris@nvidia.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-authored-by: Mark Harris <mharris@nvidia.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Rename the underlying Gaussian model from "carrier" to "gaussians" throughout the GARfVDB integration for clarity: the GARfVDB.gaussians property, the add_gaussians viewer option, the "Gaussian Splats" viewer label, and the associated variables, docstrings, messages, and docs. Also rename the on-disk names since this feature has not shipped yet (no compatibility to preserve and no ARTIFACT_SCHEMA_VERSION bump): the bundle payload gaussians.ply and its manifest key "gaussians", and the resume checkpoint keys gaussian_filter_threshold, num_gaussians, and gaussian_means_sha256. Checkpoint write/read and manifest write/read keys were updated together and verified consistent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Rename the versioned, method-neutral checkpoint structure from "envelope" to "container" across docstrings, comments, the local variables in the disk writers, test names, and the resume/checkpoints/tutorial docs. The concept and its fields (schema, schema_version, method, method_version, state) are unchanged; this is wording only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
"Validate an container" -> "Validate a container" (leftover from the envelope -> container rename). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Add a "Container schema versus method" section to the training-checkpoints docs explaining the two independent version axes: schema/schema_version version the container format (owned centrally, bumped when the wrapper structure changes), while method/method_version version the method-owned state payload (owned per method). Clarifies what lives in state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
There was a problem hiding this comment.
Pull request overview
This PR integrates GARfVDB scale-conditioned instance segmentation into fvdb-reality-capture as a first-class method, including a portable .garfvdb inference bundle format, shared checkpoint/resume infrastructure, and viewer/transform updates to support non-Gaussian derived products alongside Gaussian reconstruction.
Changes:
- Added GARfVDB training + inference product support (artifact I/O, CLI command, scene attribute/transforms, viewer overlay, and unit tests).
- Introduced a method-neutral, versioned training checkpoint container plus a CLI resume-handler registry; migrated Gaussian checkpoints to the container while keeping legacy flat Gaussian checkpoints loadable.
- Refactored common scene preprocessing into a reusable
SceneTransformConfigand updated CLI/device defaults and docs accordingly.
Reviewed changes
Copilot reviewed 68 out of 69 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_training_checkpoints.py | Tests for checkpoint container parsing/loading and resume dispatch registry behavior. |
| tests/unit/test_garfvdb_viewer.py | Unit tests for GARfVDB overlay viewer core behavior and widget-driven rerendering. |
| tests/unit/test_garfvdb_scene_transforms.py | Tests for GARfVDB mask attribute + transform ordering and cache behaviors. |
| tests/unit/test_garfvdb_cli.py | CLI contract tests for frgs instance-segmentation, frgs show, and frgs resume. |
| tests/unit/test_garfvdb_artifact.py | Tests for .garfvdb bundle format, schema versioning, validation, and round-trips. |
| tests/benchmarks/test_3dgs.py | Updated benchmark to load Gaussian checkpoints via the container loader. |
| tests/benchmarks/contract.py | Contract bump + validation through container parsing and method ID checking. |
| README.md | README updated to mention instance feature fields alongside radiance fields. |
| pyproject.toml | Adds safetensors dependency. |
| fvdb_reality_capture/transforms/scene_transform_config.py | New reusable scene transform configuration/pipeline builder. |
| fvdb_reality_capture/transforms/init.py | Exposes SceneTransformConfig in transforms public API. |
| fvdb_reality_capture/sfm_scene/sfm_cache.py | File lock updated to avoid SIGALRM timeout logic on worker threads. |
| fvdb_reality_capture/radiance_fields/io.py | New helper to load splats from PLY or checkpoint container. |
| fvdb_reality_capture/radiance_fields/gaussian_splat_reconstruction.py | Uses method version constant; default device updated to cuda:0. |
| fvdb_reality_capture/radiance_fields/gaussian_splat_reconstruction_writer.py | Writes checkpoints as versioned containers. |
| fvdb_reality_capture/radiance_fields/checkpoint.py | New Gaussian method ID/version constants + legacy adapter registration. |
| fvdb_reality_capture/radiance_fields/init.py | Re-exports method ID/version constants and load_splats_from_file. |
| fvdb_reality_capture/instance_segmentation/viewer.py | Interactive GARfVDB overlay viewer + show_garfvdb_bundle. |
| fvdb_reality_capture/instance_segmentation/util.py | New utilities incl. PCA projection helpers and mask CDF computation. |
| fvdb_reality_capture/instance_segmentation/training/segmentation_writer.py | New GARfVDB training writer (images/metrics/checkpoints). |
| fvdb_reality_capture/instance_segmentation/training/dataset.py | Segmentation dataset that loads mask attribute and recomputes mask CDF. |
| fvdb_reality_capture/instance_segmentation/training/dataset_transforms.py | Dataset transforms for mask selection, pixel sampling, resizing, etc. |
| fvdb_reality_capture/instance_segmentation/training/init.py | Training package initializer. |
| fvdb_reality_capture/instance_segmentation/scene_transforms/init.py | Exposes GenerateGARfVDBMasks. |
| fvdb_reality_capture/instance_segmentation/scene_attribute.py | New namespaced scene attribute for per-image GARfVDB mask supervision. |
| fvdb_reality_capture/instance_segmentation/optim.py | Adds Exponential LR scheduler with ramp-up for segmentation training. |
| fvdb_reality_capture/instance_segmentation/loss.py | Adds GARfVDB contrastive loss implementation and per-view normalization. |
| fvdb_reality_capture/instance_segmentation/garfvdb.py | Public GARfVDB inference product API (render, affinities, save/load). |
| fvdb_reality_capture/instance_segmentation/config.py | GARfVDB model/training/transform configs leveraging shared transform config. |
| fvdb_reality_capture/instance_segmentation/checkpoint.py | GARfVDB checkpoint method ID/version constants. |
| fvdb_reality_capture/instance_segmentation/artifact.py | Portable .garfvdb bundle save/load with schema versioning + checksums. |
| fvdb_reality_capture/instance_segmentation/init.py | Public instance-segmentation module exports. |
| fvdb_reality_capture/foundation_models/sam2.py | Default device cuda:0 + points_per_batch applied in flat mode. |
| fvdb_reality_capture/foundation_models/sam1.py | Default device updated to cuda:0. |
| fvdb_reality_capture/foundation_models/openclip.py | Default device updated to cuda:0. |
| fvdb_reality_capture/foundation_models/dlnr.py | Default device updated to cuda:0 and docstring updated. |
| fvdb_reality_capture/cli/frgs/_show.py | frgs show now supports .garfvdb bundles and GARfVDB overlay controls. |
| fvdb_reality_capture/cli/frgs/_resume.py | frgs resume now dispatches via checkpoint container + resume registry. |
| fvdb_reality_capture/cli/frgs/_resume_registry.py | New CLI-owned resume-handler registry + method lookup. |
| fvdb_reality_capture/cli/frgs/_reconstruct.py | CLI transform config now subclasses shared SceneTransformConfig; device default cuda:0. |
| fvdb_reality_capture/cli/frgs/_points.py | Device default updated to cuda:0. |
| fvdb_reality_capture/cli/frgs/_mesh_dlnr.py | Device default updated to cuda:0. |
| fvdb_reality_capture/cli/frgs/_mesh_basic.py | Device default updated to cuda:0. |
| fvdb_reality_capture/cli/frgs/_instance_segmentation.py | New frgs instance-segmentation command. |
| fvdb_reality_capture/cli/frgs/_evaluate.py | Loads Gaussian checkpoints via container loader; device default cuda:0. |
| fvdb_reality_capture/cli/frgs/_convert.py | Loads Gaussian checkpoints via container loader for conversion paths. |
| fvdb_reality_capture/cli/frgs/_common.py | Switches to library-layer load_splats_from_file and re-exports it. |
| fvdb_reality_capture/cli/frgs/init.py | Registers InstanceSegmentation command with CLI entrypoint. |
| fvdb_reality_capture/checkpoints.py | New method-neutral, versioned training checkpoint container + legacy adapters. |
| fvdb_reality_capture/init.py | Exposes new submodules (including checkpoints and instance_segmentation). |
| docs/tutorials/instance_segmentation.rst | New tutorial covering training, artifact layout, viewing, and resume. |
| docs/index.rst | Adds instance segmentation tutorial/API docs and checkpoints API page. |
| docs/api/transforms.rst | Documents SceneTransformConfig in transforms API listing. |
| docs/api/instance_segmentation.rst | New instance segmentation API page. |
| docs/api/frgs/show.rst | Updates frgs show docs for .garfvdb support and device default. |
| docs/api/frgs/resume.rst | Updates frgs resume docs for container dispatch and new options. |
| docs/api/frgs/reconstruct.rst | Updates reconstruct docs for device default cuda:0. |
| docs/api/frgs/points.rst | Updates points docs for device default cuda:0. |
| docs/api/frgs/mesh_dlnr.rst | Updates mesh-dlnr docs for device default cuda:0. |
| docs/api/frgs/mesh_basic.rst | Updates mesh-basic docs for device default cuda:0. |
| docs/api/frgs/instance_segmentation.rst | New CLI doc snippet for frgs instance-segmentation. |
| docs/api/frgs/evaluate.rst | Updates evaluate docs for device default cuda:0. |
| docs/api/frgs.rst | Updates CLI overview and includes instance-segmentation docs. |
| docs/api/checkpoints.rst | New documentation page for the training checkpoint container design. |
| CHANGES.md | Adds an unreleased 0.6.0 changelog section describing GARfVDB + infra. |
| .gitignore | Ignores .garfvdb bundles. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Change default max epochs in garfvdb Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
The ported prototype built the per-pixel mask-selection CDF by sorting the per-mask areas by size and then indexing that sorted table by mask id. After the sort the probability table no longer lined up with the mask ids, so each pixel was weighted by the wrong mask's area, and `num_pix_per_mask[0] = 0` zeroed the smallest-area mask instead of the -1 "no mask" padding (which was left in with a nonzero probability). Reimplement with a bincount indexed by (id + 1) that zeroes the -1 padding and gathers each pixel's mask probability by id. Verified against an independent reference over random inputs. Because mask_cdf is recomputed from pixel_to_mask_id at load time, the fix applies to existing caches without regenerating them; it does change the training mask-selection distribution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
- save_image: the JPEG branch multiplied an already-uint8 image by 255 again, saturating saved JPEGs to white; drop the redundant rescale (matches the PNG branch). - Resize: scale camera intrinsics with the image (multiply fx/fy/cx/cy by the scale factor) instead of dividing, so they stay consistent with the resized image. Resize is currently unused, so this was a latent bug. - segmentation_writer: use max_attempts in the retry-loop condition instead of a hard-coded 50. - docs/transforms.rst: align the inconsistent Sphinx option indentation on the Identity/SceneTransformConfig autoclass directives. Addresses GitHub Copilot review comments on openvdb#308. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…ssian affinities in the training feature space. Add regression coverage for each fix. Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…image IDs for stable pose matching, and include camera parameters in mask-cache keys. Scale intrinsics using actual rounded resize dimensions and add regression coverage. Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 72 out of 73 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
fvdb_reality_capture/instance_segmentation/training/dataset_transforms.py:82
- RandomSelectMaskIDAndScale builds random tensors via torch.full(..., torch.rand(...).item()) without specifying a device. If this transform is ever applied to CUDA tensors (e.g. after moving a batch to GPU), it will create CPU random tensors and fail with a device-mismatch error in comparisons / indexing. It’s easy to keep the intended “one random value per image” behavior while allocating on per_pixel_index.device.
Two defects in instance_segmentation/util.py flagged by Copilot on PR openvdb#308: - compute_mask_cdf weighted each mask by log(prob), which is <= 0. A pixel whose only mask has probability 1 (a lone mask, log(1) = 0) produced a per-pixel weight sum of 0, so the CDF was all zeros and the consumer (sum(u > cdf)) selected the -1 padding slot instead of the single valid mask -- that pixel got no supervision. Switch to -log(prob) (>= 0, same relative weighting so small masks stay favored) and fall back to a uniform distribution over a pixel's valid masks when the weight sum is 0, so the CDF always terminates at 1 on a real mask. Fully-unmasked pixels still resolve to padding, as before. - unique_values_to_colors computed the HSV chroma/second-component/offset (c, x, m) but always assigned (R, G, B) = (c, x, m) regardless of hue sector. With s = v = 1 that collapses every color to (1, x, 0) -- only the red->yellow sixth of the wheel -- so generated colors were not distinct. Permute (c, x, 0) per 60-degree sector and add the achromatic offset to all channels, and drop the linspace endpoint so hue 0 and hue 1 (both red) don't collide. Verified: lone-mask pixels now select the real mask, small masks remain favored, and six values map to six evenly spaced hues spanning the full wheel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…_steps == warmup_steps Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Load weights_only in GarfvdbMaskAttribute Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Background pixels (those intersecting no mask) carry a -1 padding id in mask_ids. Both mask-selection transforms indexed the per-image scales with that id directly: - RandomSelectMaskIDAndScale (CPU): scales[-1] silently selected the last mask's scale for every background pixel. - GPURandomSelectMaskIDAndScale: batch_offsets + (-1) goes negative for batch element 0 and wraps into another image's scales -- a cross-image, batch-order- dependent read. These pixels are already excluded from every loss term (loss.py masks pairs where mask_ids == -1), so there was no training-correctness impact, but the lookups were fragile. Clamp the lookup index to >= 0 in both paths and zero the resulting scale for background pixels, while keeping mask_ids as -1 so downstream loss masking is unchanged. Verified: background pixels now receive a zero scale (CPU and GPU), the GPU path no longer reads a neighbouring image's scale, and valid pixels are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…review) - viewer.py _camera_tuple_to_c2w computed the camera position from the raw eye_direction (center - eye_direction * radius), so a non-unit eye_direction made the effective orbit radius depend on its magnitude. Offset by the normalized forward instead, so the radius is exactly `radius`. Identical when eye_direction is already unit-length. - pca_projection_fast and apply_pca_projection docstrings claimed the result is always [B, H, W, n_components], but the mask-less path returns the flattened [B*H*W, n_components] projection. Document the conditional shape so callers don't assume the 4D form. (Both in-repo callers pass a mask, so behavior is unchanged; docs only.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
FileLock's SIGALRM-based timeout only works on the main thread, so worker threads (e.g. the background SfM cache writer) fell back to a blocking flock with no timeout. A lock held indefinitely by a crashed or deadlocked writer would hang that thread forever and stall training/shutdown. Worker threads now acquire the lock by polling a non-blocking flock (LOCK_NB) until it succeeds or timeout_seconds elapses, raising TimeoutError on expiry -- mirroring the main-thread timeout behavior. The acquire paths are split into _acquire_with_signal_timeout (main thread) and _acquire_with_polling_timeout (worker threads). Non-EAGAIN/EWOULDBLOCK errors (e.g. ENOSYS) still propagate and map to NotImplementedError as before; other OSErrors are now re-raised instead of being silently swallowed. Verified: with the lock held, a worker thread raises TimeoutError after ~timeout_seconds instead of hanging; once released it acquires immediately; the main-thread path is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 72 out of 73 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
docs/api/frgs/show.rst:28
- The
frgs showhelp snippet appears incomplete: the CLI now exposes GARfVDB-specific flags (e.g. --scale-fraction, --mask-blend, --lock-pca-colors, overlay sizing), but they are not reflected here. This documentation will be out of sync with the actual CLI interface.
│ -v, --verbose, --no-verbose │
│ If True, then the viewer will log verbosely. (default: False) │
│ --device STR|DEVICE Device to use for computation (default is "cuda:0"). (default: cuda:0) │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
The help for --viewer-ip-address was a copy-paste from --viewer-port and described "The port to expose the viewer server on." tyro derives the help string from the comment above the field, so both the CLI and the captured docs showed the wrong description for an IP-address option. Correct the source comment in _show.py and _show_data.py (the frgs resume, reconstruct, and instance-segmentation variants already read "IP address") and update the captured show.rst / show_data.rst help snippets to match, preserving the box-drawing column width. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Per PR review (harrism), rename the CLI method to `frgs segment-instances`. tyro derives the subcommand name from the command class, so: - Rename cli/frgs/_instance_segmentation.py -> _segment_instances.py and the classes InstanceSegmentation -> SegmentInstances and InstanceSegmentationWriterConfig -> SegmentInstancesWriterConfig, matching the repo's _<command>.py convention. - Update the frgs command union, the docstring example, and the use-grid error. - Update tests/unit/test_garfvdb_cli.py import, target, and test name. - Rename docs/api/frgs/instance_segmentation.rst -> segment_instances.rst, update its usage/options/example text, and update the frgs.rst section header and include path. - Update the tutorial command example and CHANGES.md. The instance_segmentation Python package, the instance_segmentation.garfvdb checkpoint method ID, and prose describing the concept are unchanged -- only the user-facing command name is renamed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Summary
frgs segment-instances), where an existing Gaussian reconstruction is consumed as input and the learned scale-conditioned feature field is the product..garfvdbbundle containing NanoVDBs (encoder grids and features), safetensors (dense network weights and scale-quantile buffers), and a PLY Gaussian model, with explicit schema versioning, checksums, and validation on load. This is the trained end-product of a GARfVDB model (i.e. the equivalent of a.plyfile for the optimized Gaussian splat reconstruction scene), not a resumable checkpoint.> frgs segment-instances DATASET --reconstruction-path scene.ply --out-path scene.garfvdbtrains from an existing reconstruction and writes a portable bundleMotivation
GARfVDB previously lived as a standalone project in fvdb-examples that was already coupled to fvdb-reality-capture for SfM loading and Gaussian reconstruction, but its deliverable is a second learned product (based on a filtered Gaussian scene) rather than more Gaussians. This is the first fvdb-reality-capture method whose product is not a Gaussian splat, so the integration deliberately separates shared workflow infrastructure from product-specific behavior instead of bolting a training script onto the Gaussian commands. The shared pieces are designed against a concrete second method (for example integrating the LangSplatV2 method that also uses SAM2) so they generalize rather than hard-coding GARfVDB.
Artifact contract
The
.garfvdb/bundle is the trained end-product of the instance segmentation method (equivalent to what the.plyfile is to thereconstructprocess).*.garfvdb/bundle contains no PyTorch pickle files:manifest.json,encoder.nvdb,network.safetensors, andgaussians.ply.encoder.nvdbstores all encoder grid topologies and their learned per-voxel features through fVDB NanoVDB I/O with compression; grids are named deterministically (encoder_00...encoder_NN) and that order is validated on load.network.safetensorsstores the MLP weights, optional sparse-convolution weights, maximum scale, and scale-quantile lookup buffers as named dense tensors.gaussians.plystores the exact filteredGaussianSplat3dmodel plus reconstruction and camera metadata.Note: The manifest carries an explicit integer
schema_versionand per-payload SHA-256 checksums. Loading dispatches through a version-specific reader that validates canonical grid names and order, grid/voxel counts, voxel sizes and origins, feature shape and dtype, Gaussian count, and every payload checksum. Missing, renamed, reordered, corrupted, or incompatible payloads fail with clear errors, and newer-than-supported bundle versions fail before any payload is read.Checkpoint and resume architecture
To accommodate checkpoints beyond the reconstruction method checkpoints being resumed (and without specific methods needed to be invoked by users
resume-instance-segmentation, etc.), this method formalizes the checkpoint container concept.schema,schema_version,method,method_version, and method-ownedstate. Parsing first validates the container, selects an exact schema-version reader, and can consult registered legacy adapters for pre-container formats.schema/schema_versionversion the container format itself — the fixed top-level keys and how they are parsed;schemais a constant shared by every method andschema_versionis owned centrally, bumped only when the wrapper structure changes (which affects all methods at once).method/method_versionversion the method-owned payload instate:methodis the stable id of the producing method andmethod_version, owned by the method's package as a single source of truth, versions that method'sstatelayout and is bumped only when that one method's state changes. Everything method-specific — model weights, optimizer/scheduler state, config, global step, and method metadata — lives opaquely insidestate; the container layer never interprets it.radiance_fields.checkpointdefines the Gaussian method id (radiance_fields.gaussian_splat) and a legacy adapter for released flat Gaussian checkpoints;instance_segmentation.checkpointdefines the GARfVDB method id (instance_segmentation.garfvdb).state_dict/from_state_dictand its disk writer container derive from, so the recorded container version and the method state version cannot drift.frgs resumeloads one validated container, resolves the stable method id, and invokes a CLI-owned resume handler that also owns the default output name. Unknown methods fail explicitly.Transform and scene-attribute integration
So that a second, non-Gaussian method can reuse the reconstruction's scene preprocessing without duplicating it or overwriting shared scene state, the standard transform stages are factored into a reusable
SceneTransformConfig, and each method's per-image supervision is carried as a namespaced scene attribute rather than by replacing the scene cache.SceneTransformConfigbuilds the standard alignment, point filtering, image downsampling, low-point image filtering, and cropping stages; both reconstruction and GARfVDB build on it. Reconstruction supplies normalization while GARfVDB injects the reconstruction's saved alignment transform.GARfVDBMaskAttributeonSfmScenerather than replacing the scene cache, so existing caches and attributes are preserved and the dataset consumes only its named attribute.Interactive viewer
Updated the viewer to be able to visualize the segmentation mask output (using PCA to project the mask to a 3-channel RGB visualization) as an overlay with interactive parameters to control the visualization.
GARfVDBOverlayViewer, which renders the GARfVDB feature field as a live overlay on the Gaussians in the fvdb viewer with interactive "Scene Params" widgets:frgs showviewer and a new live training viewer enabled withfrgs segment-instances --viewer, which pumps overlay renders from the training loop between steps so the in-progress feature field can be inspected while training runs.SAM2 mask-generation performance
In addition to porting the SAM2 mask-generation transforms from
fvdb-examples, this PR also makes some performance improvements during the transition:pixel_to_mask_idconstruction becomes a single cumulative-sum plus scatter instead of anO(masks * max_overlap)Python loop that forced a device synchronization on every inner iteration, and per-mask scale computation deduplicates on integer gaussian ids rather than sorting float3 world points. Both are equivalent to the originals.[H, W, max_overlap]float32 tensor) and recomputes it frompixel_to_mask_idat load time viainstance_segmentation.util.compute_mask_cdf. This roughly quarters both the on-disk artifact size and the disk-bound write time.SfmCache'sFileLocknow only arms its SIGALRM-based timeout on the main thread and falls back to a plain blockingflockon worker threads, since signal-based timeouts cannot be used off the main thread.points_per_batchso more point prompts are processed per mask-decoder forward pass.Device defaults and import updates
cuda:0across the CLI, foundation models, config, and docs, because the current fvdb build requires an explicit device index andcudaalone raises "Device must specify an index".Dependencies
fvdb-core>=0.6.0and addssafetensors.fvdb.vizfeatures that bring the API changes to add interactive, definable parameters require this PR to merge before this branch will work: Add viewer widgets fvdb-core#649Documentation
Testing
unit/test_garfvdb_viewer.py(widget registration, normalized-to-raw scale mapping, camera/widget change detection and the render/idle contract, show/hide, and PCA lock), and the vectorized mask post-processing and recomputed mask CDF were checked for equivalence against the original implementations, including against a real on-disk cache file.Backwards compatibility and follow-ups
.plyremains an export product and is not resumable.use_grid_conv=Trueremains experimental and is not exercised by the first-class path; the per-Gaussian affinity path references a grid attribute that does not exist and would fail if enabled. First-class training and products requireuse_grid=True, so this does not affect the supported configuration, but the option should be fixed or gated before it is advertised as supported.image2d.slangcomposited the image view opaquely and ignored per-pixel alpha) Fix compositing image2d over the scene using per-pixel alpha nanovdb-editor#217