ffgs 0.1.0.dev0__tar.gz → 0.1.0.dev1__tar.gz

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.
Files changed (120) hide show
  1. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/PKG-INFO +29 -11
  2. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/README.md +26 -10
  3. ffgs-0.1.0.dev1/docs/models/c3g.md +89 -0
  4. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/docs/models/index.md +3 -2
  5. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/pyproject.toml +8 -4
  6. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/__init__.py +1 -0
  7. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/processing.py +1 -0
  8. ffgs-0.1.0.dev1/src/ffgs/models/c3g/LICENSE +650 -0
  9. ffgs-0.1.0.dev1/src/ffgs/models/c3g/__init__.py +51 -0
  10. ffgs-0.1.0.dev1/src/ffgs/models/c3g/gmae.py +89 -0
  11. ffgs-0.1.0.dev1/src/ffgs/models/c3g/modeling.py +103 -0
  12. ffgs-0.1.0.dev1/src/ffgs/models/c3g/processing.py +95 -0
  13. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/__init__.py +0 -0
  14. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/aggregator.py +355 -0
  15. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/heads/__init__.py +0 -0
  16. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/heads/dpt_head.py +599 -0
  17. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/heads/head_act.py +130 -0
  18. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/heads/utils.py +127 -0
  19. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/__init__.py +23 -0
  20. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/attention.py +86 -0
  21. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/block.py +150 -0
  22. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/drop_path.py +39 -0
  23. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/layer_scale.py +26 -0
  24. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/mlp.py +43 -0
  25. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/patch_embed.py +100 -0
  26. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/rope.py +209 -0
  27. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/swiglu_ffn.py +74 -0
  28. ffgs-0.1.0.dev1/src/ffgs/models/c3g/vggt/layers/vision_transformer.py +448 -0
  29. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/__init__.py +1 -0
  30. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/processing.py +5 -0
  31. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/pipeline.py +9 -9
  32. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/processor.py +19 -6
  33. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/registry.py +3 -0
  34. ffgs-0.1.0.dev1/src/ffgs/zoo/c3g/default.json +21 -0
  35. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/_dummy.py +15 -0
  36. ffgs-0.1.0.dev1/tests/fixtures/c3g_checkpoint.json +8220 -0
  37. ffgs-0.1.0.dev1/tests/fixtures/c3g_reference.safetensors +0 -0
  38. ffgs-0.1.0.dev1/tests/fixtures/make_c3g_reference.py +286 -0
  39. ffgs-0.1.0.dev1/tests/test_c3g.py +263 -0
  40. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_pipeline.py +17 -0
  41. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/uv.lock +12 -1
  42. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/.gitignore +0 -0
  43. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/LICENSE +0 -0
  44. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/docs/models/anysplat.md +0 -0
  45. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/docs/models/tokengs.md +0 -0
  46. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/examples/infer_nerfstudio_scene.py +0 -0
  47. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/examples/infer_pose_free.py +0 -0
  48. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/__init__.py +0 -0
  49. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/dynamic.py +0 -0
  50. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/geometry.py +0 -0
  51. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/image.py +0 -0
  52. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/__init__.py +0 -0
  53. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/LICENSE +0 -0
  54. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/encoder.py +0 -0
  55. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/evaluation.py +0 -0
  56. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/gaussian_adapter.py +0 -0
  57. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/gs_head.py +0 -0
  58. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/modeling.py +0 -0
  59. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/__init__.py +0 -0
  60. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/heads/__init__.py +0 -0
  61. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/heads/camera_head.py +0 -0
  62. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/heads/dpt_head.py +0 -0
  63. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/heads/head_act.py +0 -0
  64. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/heads/utils.py +0 -0
  65. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/__init__.py +0 -0
  66. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/attention.py +0 -0
  67. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/block.py +0 -0
  68. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/drop_path.py +0 -0
  69. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/layer_scale.py +0 -0
  70. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/mlp.py +0 -0
  71. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/patch_embed.py +0 -0
  72. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/rope.py +0 -0
  73. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/swiglu_ffn.py +0 -0
  74. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/layers/vision_transformer.py +0 -0
  75. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/models/__init__.py +0 -0
  76. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/models/aggregator.py +0 -0
  77. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/utils/__init__.py +0 -0
  78. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/utils/geometry.py +0 -0
  79. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/utils/pose_enc.py +0 -0
  80. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/anysplat/vggt/utils/rotation.py +0 -0
  81. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/activations.py +0 -0
  82. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/attention.py +0 -0
  83. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/enc_dec.py +0 -0
  84. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/models/tokengs/modeling.py +0 -0
  85. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/render.py +0 -0
  86. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/types.py +0 -0
  87. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/__init__.py +0 -0
  88. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/anysplat/default.json +0 -0
  89. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-2v.json +0 -0
  90. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-4v.json +0 -0
  91. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-6v.json +0 -0
  92. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-base.json +0 -0
  93. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-2v-lpips.json +0 -0
  94. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-2v-ssim.json +0 -0
  95. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-4v-lpips.json +0 -0
  96. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-4v-ssim.json +0 -0
  97. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-6v-lpips.json +0 -0
  98. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/src/ffgs/zoo/tokengs/dl3dv-latent-6v-ssim.json +0 -0
  99. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/_empty.py +0 -0
  100. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/_numeric.py +0 -0
  101. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/conftest.py +0 -0
  102. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/anysplat_checkpoint.json +0 -0
  103. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/anysplat_reference.safetensors +0 -0
  104. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/make_anysplat_reference.py +0 -0
  105. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/make_tokengs_reference.py +0 -0
  106. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/reference.safetensors +0 -0
  107. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/tokengs_checkpoints.json +0 -0
  108. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/fixtures/tokengs_reference.safetensors +0 -0
  109. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_anysplat.py +0 -0
  110. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_boundary.py +0 -0
  111. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_data_path.py +0 -0
  112. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_examples.py +0 -0
  113. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_geometry.py +0 -0
  114. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_image.py +0 -0
  115. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_pose_free.py +0 -0
  116. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_render.py +0 -0
  117. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_third_party.py +0 -0
  118. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_tokengs.py +0 -0
  119. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_types.py +0 -0
  120. {ffgs-0.1.0.dev0 → ffgs-0.1.0.dev1}/tests/test_zoo.py +0 -0
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ffgs
3
- Version: 0.1.0.dev0
3
+ Version: 0.1.0.dev1
4
4
  Summary: Standard inference for feed-forward 3D Gaussian Splatting models
5
5
  Project-URL: Repository, https://github.com/libgs/ffgs
6
6
  Author: libgs contributors
7
7
  License-Expression: Apache-2.0 AND MIT AND CC-BY-NC-4.0 AND CC-BY-NC-SA-4.0
8
8
  License-File: LICENSE
9
9
  License-File: src/ffgs/models/anysplat/LICENSE
10
+ License-File: src/ffgs/models/c3g/LICENSE
10
11
  Classifier: Programming Language :: Python :: 3
11
12
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
13
  Requires-Python: >=3.10
14
+ Requires-Dist: einops>=0.8.2
13
15
  Requires-Dist: huggingface-hub>=0.23
14
16
  Requires-Dist: numpy
15
17
  Requires-Dist: pillow
@@ -62,12 +64,13 @@ python examples/infer_nerfstudio_scene.py <scene> --model <family>/<variant> \
62
64
  --images-dir <scene>/images_4 --out out
63
65
  ```
64
66
 
65
- Models that predict their own cameras also run on images alone. There is no world
66
- frame then: the Gaussians and the predicted input cameras are both in the model's
67
- frame, and renders take cameras in that frame (see [Conventions](#conventions)):
67
+ Models that do not need poses (`camera_input` "optional" or "none") also run on
68
+ images alone. There is no world frame then: the Gaussians, and the input cameras
69
+ for models that predict them, are in the model's frame, and renders take cameras
70
+ in that frame (see [Conventions](#conventions)):
68
71
 
69
72
  ```python
70
- pipe = GSPipeline.from_pretrained("<family>/<variant>", device="cuda") # a pose-free model
73
+ pipe = GSPipeline.from_pretrained("<family>/<variant>", device="cuda") # e.g. anysplat/default
71
74
  out = pipe(Views(images)) # no intrinsics, no c2w: out.frame is None
72
75
  out.gaussians.save_ply("scene.ply") # in the model's frame
73
76
  renders = pipe.render(out, out.cameras)["images"] # the inputs, at the predicted cameras
@@ -96,7 +99,7 @@ Check the license of a model before use: some are for non-commercial use only.
96
99
  | | |
97
100
  |---|---|
98
101
  | images | float in [0, 1] (uint8 accepted), `[V, 3, H, W]` or `[B, V, 3, H, W]`, any resolution |
99
- | c2w | camera-to-world, OpenCV axes (x right, y down, z forward); optional for models that predict their own cameras |
102
+ | c2w | camera-to-world, OpenCV axes (x right, y down, z forward); optional unless the model requires them (`camera_input="required"`) |
100
103
  | intrinsics | pixel K for the image size given, or normalised K with `normalized_intrinsics=True`; optional like c2w |
101
104
  | output | `GSOutput`: `Gaussians` (means, scales, wxyz quats, opacities, SH or RGB colours) in the model's frame, that frame (or `None`), and the input cameras the model predicted (or `None`) |
102
105
 
@@ -220,16 +223,22 @@ pipe = GSPipeline.from_pretrained("owner/family-repo", subfolder="variant",
220
223
  ## Adding a model
221
224
 
222
225
  A model is a model class with `PyTorchModelHubMixin`, a `Processor` subclass
223
- (`preprocess`, returning the model input and its `ModelFrame`, or `None` when the
224
- model predicts its own cameras; `postprocess`, to model-frame `Gaussians`;
226
+ (`preprocess`, returning the model input and its `ModelFrame`, or `None` without
227
+ input poses or when the model predicts its own cameras; `postprocess`, to model-frame `Gaussians`;
225
228
  `render_planes`, near / far in world units, or in model units when the frame is
226
229
  `None`; for models that predict cameras, `predicted_cameras`, from the model output
227
230
  to model-frame `Cameras`, and `output_frame`, whose default fits the frame to the
228
231
  given c2w as above; optionally `background` and `render_settings`, the model's
229
232
  rasteriser settings such as `radius_clip`, `rasterize_mode` or `clamp`, which
230
- `pipe.render` uses unless the caller passes them) and a
231
- `ModelSpec(model_type, model_cls, processor_cls)`. `from_pretrained` resolves the
232
- `model_type` in `config.json` in this order:
233
+ `pipe.render` uses unless the caller passes them; `camera_input`, whether the
234
+ model is conditioned on the input cameras: `"required"` (the default, always),
235
+ `"optional"` (when given) or `"none"` (never);
236
+ for models trained on poses of a fixed scale, a `camera_spread` in the config, the
237
+ mean distance of the input cameras to the first one in the training data) and a
238
+ `ModelSpec(model_type, model_cls, processor_cls, display_name=...)`. Applications
239
+ read `camera_input`, `camera_spread` and `display_name` to handle a new
240
+ model without changes. `from_pretrained` resolves the `model_type` in
241
+ `config.json` in this order:
233
242
 
234
243
  1. **In your code** — `ffgs.register_model(spec)` before loading (built-in models
235
244
  are registered the same way, on first use).
@@ -303,6 +312,13 @@ torch first (see `.github/workflows/ci.yml`). `tests/fixtures/reference.safetens
303
312
  holds frozen reference outputs of the default data path; change it only together
304
313
  with a deliberate change to that path.
305
314
 
315
+ To release, set `version` in `pyproject.toml` (then `uv lock`), commit it as
316
+ `chore: release X.Y.Z`, merge it into main and push the tag `vX.Y.Z` on that commit.
317
+ `.github/workflows/release.yml` then runs CI, publishes to PyPI and creates the
318
+ [GitHub release](https://github.com/libgs/ffgs/releases), whose notes are generated
319
+ by [git-cliff](https://git-cliff.org/) from the conventional commits since the
320
+ previous tag (`cliff.toml`; preview with `uvx git-cliff --unreleased`).
321
+
306
322
  ## License
307
323
 
308
324
  **The ffgs package as distributed is not entirely Apache-2.0: it includes code for
@@ -314,6 +330,8 @@ non-commercial use only.** Its license expression is
314
330
  - vendored model code under `src/ffgs/models/<family>/` keeps its upstream license.
315
331
  `src/ffgs/models/anysplat/` mixes MIT, Apache-2.0 and non-commercial
316
332
  (CC-BY-NC-4.0, CC-BY-NC-SA-4.0) files; its `LICENSE` lists the license per file;
333
+ `src/ffgs/models/c3g/` mixes MIT, Apache-2.0 and VGGT files kept with the
334
+ CC-BY-NC-4.0 text; its `LICENSE` describes which is which;
317
335
  - model weights are not part of the package and carry their own licenses.
318
336
 
319
337
  The code and weight licenses of each model are listed in
@@ -40,12 +40,13 @@ python examples/infer_nerfstudio_scene.py <scene> --model <family>/<variant> \
40
40
  --images-dir <scene>/images_4 --out out
41
41
  ```
42
42
 
43
- Models that predict their own cameras also run on images alone. There is no world
44
- frame then: the Gaussians and the predicted input cameras are both in the model's
45
- frame, and renders take cameras in that frame (see [Conventions](#conventions)):
43
+ Models that do not need poses (`camera_input` "optional" or "none") also run on
44
+ images alone. There is no world frame then: the Gaussians, and the input cameras
45
+ for models that predict them, are in the model's frame, and renders take cameras
46
+ in that frame (see [Conventions](#conventions)):
46
47
 
47
48
  ```python
48
- pipe = GSPipeline.from_pretrained("<family>/<variant>", device="cuda") # a pose-free model
49
+ pipe = GSPipeline.from_pretrained("<family>/<variant>", device="cuda") # e.g. anysplat/default
49
50
  out = pipe(Views(images)) # no intrinsics, no c2w: out.frame is None
50
51
  out.gaussians.save_ply("scene.ply") # in the model's frame
51
52
  renders = pipe.render(out, out.cameras)["images"] # the inputs, at the predicted cameras
@@ -74,7 +75,7 @@ Check the license of a model before use: some are for non-commercial use only.
74
75
  | | |
75
76
  |---|---|
76
77
  | images | float in [0, 1] (uint8 accepted), `[V, 3, H, W]` or `[B, V, 3, H, W]`, any resolution |
77
- | c2w | camera-to-world, OpenCV axes (x right, y down, z forward); optional for models that predict their own cameras |
78
+ | c2w | camera-to-world, OpenCV axes (x right, y down, z forward); optional unless the model requires them (`camera_input="required"`) |
78
79
  | intrinsics | pixel K for the image size given, or normalised K with `normalized_intrinsics=True`; optional like c2w |
79
80
  | output | `GSOutput`: `Gaussians` (means, scales, wxyz quats, opacities, SH or RGB colours) in the model's frame, that frame (or `None`), and the input cameras the model predicted (or `None`) |
80
81
 
@@ -198,16 +199,22 @@ pipe = GSPipeline.from_pretrained("owner/family-repo", subfolder="variant",
198
199
  ## Adding a model
199
200
 
200
201
  A model is a model class with `PyTorchModelHubMixin`, a `Processor` subclass
201
- (`preprocess`, returning the model input and its `ModelFrame`, or `None` when the
202
- model predicts its own cameras; `postprocess`, to model-frame `Gaussians`;
202
+ (`preprocess`, returning the model input and its `ModelFrame`, or `None` without
203
+ input poses or when the model predicts its own cameras; `postprocess`, to model-frame `Gaussians`;
203
204
  `render_planes`, near / far in world units, or in model units when the frame is
204
205
  `None`; for models that predict cameras, `predicted_cameras`, from the model output
205
206
  to model-frame `Cameras`, and `output_frame`, whose default fits the frame to the
206
207
  given c2w as above; optionally `background` and `render_settings`, the model's
207
208
  rasteriser settings such as `radius_clip`, `rasterize_mode` or `clamp`, which
208
- `pipe.render` uses unless the caller passes them) and a
209
- `ModelSpec(model_type, model_cls, processor_cls)`. `from_pretrained` resolves the
210
- `model_type` in `config.json` in this order:
209
+ `pipe.render` uses unless the caller passes them; `camera_input`, whether the
210
+ model is conditioned on the input cameras: `"required"` (the default, always),
211
+ `"optional"` (when given) or `"none"` (never);
212
+ for models trained on poses of a fixed scale, a `camera_spread` in the config, the
213
+ mean distance of the input cameras to the first one in the training data) and a
214
+ `ModelSpec(model_type, model_cls, processor_cls, display_name=...)`. Applications
215
+ read `camera_input`, `camera_spread` and `display_name` to handle a new
216
+ model without changes. `from_pretrained` resolves the `model_type` in
217
+ `config.json` in this order:
211
218
 
212
219
  1. **In your code** — `ffgs.register_model(spec)` before loading (built-in models
213
220
  are registered the same way, on first use).
@@ -281,6 +288,13 @@ torch first (see `.github/workflows/ci.yml`). `tests/fixtures/reference.safetens
281
288
  holds frozen reference outputs of the default data path; change it only together
282
289
  with a deliberate change to that path.
283
290
 
291
+ To release, set `version` in `pyproject.toml` (then `uv lock`), commit it as
292
+ `chore: release X.Y.Z`, merge it into main and push the tag `vX.Y.Z` on that commit.
293
+ `.github/workflows/release.yml` then runs CI, publishes to PyPI and creates the
294
+ [GitHub release](https://github.com/libgs/ffgs/releases), whose notes are generated
295
+ by [git-cliff](https://git-cliff.org/) from the conventional commits since the
296
+ previous tag (`cliff.toml`; preview with `uvx git-cliff --unreleased`).
297
+
284
298
  ## License
285
299
 
286
300
  **The ffgs package as distributed is not entirely Apache-2.0: it includes code for
@@ -292,6 +306,8 @@ non-commercial use only.** Its license expression is
292
306
  - vendored model code under `src/ffgs/models/<family>/` keeps its upstream license.
293
307
  `src/ffgs/models/anysplat/` mixes MIT, Apache-2.0 and non-commercial
294
308
  (CC-BY-NC-4.0, CC-BY-NC-SA-4.0) files; its `LICENSE` lists the license per file;
309
+ `src/ffgs/models/c3g/` mixes MIT, Apache-2.0 and VGGT files kept with the
310
+ CC-BY-NC-4.0 text; its `LICENSE` describes which is which;
295
311
  - model weights are not part of the package and carry their own licenses.
296
312
 
297
313
  The code and weight licenses of each model are listed in
@@ -0,0 +1,89 @@
1
+ # C3G
2
+
3
+ C3G ([paper](https://arxiv.org/abs/2512.04021),
4
+ [code](https://github.com/cvlab-kaist/C3G)) predicts a fixed budget of 2048
5
+ Gaussians from unposed images: learnable query tokens decode the features of a
6
+ VGGT aggregator. It predicts no cameras, and poses never enter the model: given
7
+ c2w only fix the output frame (see [Conventions](../../README.md#conventions)).
8
+
9
+ ## Licenses
10
+
11
+ The code is vendored from the official code (`src/ffgs/models/c3g/`, Gaussian
12
+ inference only), each file under its own license: MIT (C3G), Apache-2.0 (DINOv2
13
+ layers) and the VGGT files with upstream's root-license pointer, kept with the
14
+ CC BY-NC 4.0 text. **The C3G model directory as a whole is for non-commercial
15
+ use only**; see its [`LICENSE`](../../src/ffgs/models/c3g/LICENSE). The authors
16
+ declare no license for the weights; they are fine-tuned from VGGT-1B, whose
17
+ weights are CC BY-NC 4.0 (non-commercial). The zoo downloads them from the
18
+ authors' repository; ffgs does not redistribute them.
19
+
20
+ ## Zoo entries
21
+
22
+ | zoo name | model | input views | input size | weights |
23
+ |---|---|---|---|---|
24
+ | `c3g/default` | the released multiview Gaussian decoder: 2048 Gaussians, SH degree 0 | 2 or more | 224×224 | [honggyuAn/C3G](https://huggingface.co/honggyuAn/C3G) (`gaussian_decoder_multiview.ckpt`) |
25
+
26
+ The checkpoint is the authors' Lightning checkpoint (4.6 GB, with the frozen
27
+ DPT head it was trained with); the zoo pins its revision and SHA-256 and loads
28
+ the encoder strictly. The 2-view checkpoint and the feature decoders (LSeg,
29
+ DINOv2, DINOv3) are not in the zoo.
30
+
31
+ ## Usage
32
+
33
+ Without poses there is no world frame and no cameras: the Gaussians are in the
34
+ model's frame (the first camera at the origin, looking down +z), and renders take
35
+ cameras in that frame:
36
+
37
+ ```python
38
+ from ffgs import GSPipeline, Views
39
+
40
+ pipe = GSPipeline.from_pretrained("c3g/default", device="cuda")
41
+ out = pipe(Views(images)) # out.frame and out.cameras are None
42
+ out.gaussians.save_ply("scene.ply") # in the model's frame
43
+ ```
44
+
45
+ With poses the output is placed in their frame, as upstream normalises the scene:
46
+ the first camera as the anchor and the distance between the first and last
47
+ cameras as the unit (it must lie in [1e-3, 1e10]):
48
+
49
+ ```python
50
+ out = pipe(Views(images, intrinsics, c2w)) # c2w do not change the prediction
51
+ out.to_world().gaussians.save_ply("scene.ply") # in the frame of c2w
52
+ renders = pipe.render(out, target_cameras)["images"] # cameras in that frame
53
+ ```
54
+
55
+ The processor resizes with PIL Lanczos and centre-crops to 224×224, as upstream
56
+ does. It renders as upstream: black background, no radius clip, no colour clamp.
57
+ Upstream's renderer is scale-invariant (it rescales near / far and the scene);
58
+ ffgs renders with gsplat at the nominal near / far 0.1 / 100, in the units of the
59
+ given c2w (of the model frame without poses).
60
+
61
+ ## Results on RealEstate10K
62
+
63
+ `c3g/default` and the official code (cvlab-kaist/C3G 40b2cc9: the encoder and its
64
+ CUDA renderer, two context views, `test.align_pose=false`) on the same weights,
65
+ the first three scenes of the authors' RE10K evaluation index, each rendered at
66
+ its target cameras (normalised as upstream: first context camera, endpoint
67
+ baseline). This checks that ffgs runs the model as upstream does; it does not
68
+ reproduce the paper (no pose optimisation, three scenes).
69
+
70
+ | scene | official vs ffgs renders, PSNR | official vs GT, PSNR | ffgs vs GT, PSNR |
71
+ |---|---|---|---|
72
+ | `000c3ab189999a83` | 38.6 / 37.8 / 37.6 | 16.35 / 16.39 / 16.31 | 16.37 / 16.37 / 16.29 |
73
+ | `000db54a47bd43fe` | 37.0 / 35.5 / 35.2 | 21.45 / 20.76 / 20.24 | 21.49 / 20.83 / 20.31 |
74
+ | `0017ce4c6a39d122` | 40.0 / 40.2 / 40.0 | 19.20 / 16.82 / 16.61 | 19.15 / 16.81 / 16.60 |
75
+
76
+ - **Inputs**: the preprocessed context and target images are identical; the
77
+ normalised target cameras agree to 1e-7.
78
+ - **Prediction**: on the CPU, the official encoder and ffgs give bit-identical
79
+ aggregator tokens and Gaussian parameters. On CUDA neither is reproducible:
80
+ run twice on the same input, the official model itself moves about 175 of the
81
+ 2048 Gaussians by more than 1e-2, as many as official vs ffgs. The cause is the
82
+ head's attention: in fp32, `scaled_dot_product_attention` runs PyTorch's
83
+ memory-efficient kernel (`fmha_cutlassF_f32_aligned_64x128_rf_sm80`, measured on
84
+ an RTX PRO 6000 Blackwell with torch 2.7.1), whose output varies from run to run,
85
+ also under `torch.use_deterministic_algorithms(True)`; the second decoder layer
86
+ amplifies it. Every other op of the head is bit-reproducible, and so is the whole
87
+ head with the math backend. ffgs keeps upstream's default backend.
88
+ - **Rendering**: the remaining render difference comes from that and from the
89
+ renderers (upstream's scale-invariant CUDA rasteriser vs gsplat).
@@ -3,10 +3,11 @@
3
3
  The models ffgs ships. Each has a page with its source, licenses, zoo entries,
4
4
  usage notes and results against the official code.
5
5
 
6
- | model | zoo names | poses | input views | input size | license (code / weights) | page |
6
+ | model | zoo names | poses (`camera_input`) | input views | input size | license (code / weights) | page |
7
7
  |---|---|---|---|---|---|---|
8
8
  | TokenGS | `tokengs/*` | required | 2 / 4 / 6 | 256×256, 256×448 | Apache-2.0 / non-commercial | [tokengs.md](tokengs.md) |
9
- | AnySplat | `anysplat/default` | optional (predicts the cameras) | any | 448×448 | non-commercial / non-commercial | [anysplat.md](anysplat.md) |
9
+ | AnySplat | `anysplat/default` | none (given poses only place the output) | any | 448×448 | non-commercial / non-commercial | [anysplat.md](anysplat.md) |
10
+ | C3G | `c3g/default` | none (given poses only place the output) | 2 or more | 224×224 | non-commercial / non-commercial (undeclared, VGGT-derived) | [c3g.md](c3g.md) |
10
11
 
11
12
  `ffgs.zoo.list_models()` lists every zoo name; each zoo entry also records its
12
13
  source, pinned revision and license.
@@ -1,12 +1,15 @@
1
1
  [project]
2
2
  name = "ffgs"
3
- version = "0.1.0.dev0"
3
+ version = "0.1.0.dev1"
4
4
  description = "Standard inference for feed-forward 3D Gaussian Splatting models"
5
5
  readme = "README.md"
6
- # The AnySplat model (src/ffgs/models/anysplat/) includes non-commercial code: see
7
- # its LICENSE.
6
+ # AnySplat and C3G include non-commercial components: see each model's LICENSE.
8
7
  license = "Apache-2.0 AND MIT AND CC-BY-NC-4.0 AND CC-BY-NC-SA-4.0"
9
- license-files = ["LICENSE", "src/ffgs/models/anysplat/LICENSE"]
8
+ license-files = [
9
+ "LICENSE",
10
+ "src/ffgs/models/anysplat/LICENSE",
11
+ "src/ffgs/models/c3g/LICENSE",
12
+ ]
10
13
  authors = [{ name = "libgs contributors" }]
11
14
  requires-python = ">=3.10"
12
15
  classifiers = [
@@ -14,6 +17,7 @@ classifiers = [
14
17
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
15
18
  ]
16
19
  dependencies = [
20
+ "einops>=0.8.2",
17
21
  "huggingface_hub>=0.23",
18
22
  "numpy",
19
23
  "pillow",
@@ -33,6 +33,7 @@ SPEC = register_model(
33
33
  model_cls=AnySplat,
34
34
  processor_cls=AnySplatProcessor,
35
35
  convert_state_dict=convert_state_dict,
36
+ display_name="AnySplat",
36
37
  )
37
38
  )
38
39
 
@@ -77,6 +77,7 @@ class AnySplatProcessorConfig(ImageFitConfig):
77
77
 
78
78
  class AnySplatProcessor(Processor):
79
79
  config_cls = AnySplatProcessorConfig
80
+ camera_input = "none"
80
81
 
81
82
  def preprocess(self, views: Views, device: torch.device) -> Prepared:
82
83
  images = self.fit_views(views).images