Lucasgitguy-co commited on
Commit
1648582
·
verified ·
1 Parent(s): 6ea4e70

Release evaluated Forma3D Tiny 2 continuation

Browse files
ATTRIBUTIONS.json ADDED
The diff for this file is too large to render. See raw diff
 
COMMANDS.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Forma3D Tiny 2 on an M4 MacBook Air with 16 GB RAM
2
+
3
+ This is a proposed weights-only continuation of `Lucasgitguy-co/forma3d-tiny`,
4
+ named `Lucasgitguy-co/forma3d-tiny-2`. It preserves the small FP32 architecture:
5
+ 306,321 parameters, grid 16 cubed, decoder width 64. It aims to improve coarse
6
+ proxy reconstruction through more object diversity. No v2 training has been run
7
+ as part of preparing this package, and improvement is not guaranteed.
8
+
9
+ ## Run the commands
10
+
11
+ Extract the kit into a local folder. Open macOS Terminal in that folder. Use a
12
+ folder stored locally rather than an offloaded iCloud folder. Keep the Mac on
13
+ power and allow cooling during a sustained run.
14
+
15
+ ```bash
16
+ # If uv is not installed, install it with Homebrew first.
17
+ brew install uv
18
+
19
+ # From the extracted forma3d-tiny-2-kit folder:
20
+ bash posttrain.sh setup
21
+ bash posttrain.sh prepare
22
+ bash posttrain.sh train
23
+ bash posttrain.sh evaluate
24
+ ```
25
+
26
+ Run each command only after the previous one succeeds. Setup downloads the source
27
+ and current v1 weights at the fixed revision
28
+ `e80b8d895cc2e405894473abe4ce40b3472eaca7`. It installs Python 3.11 and the
29
+ publisher's tested dependency lock in an isolated environment under
30
+ `~/Library/Application Support/Forma3D-tiny-2/venv`. Setup does not train or upload.
31
+ `image3d doctor` must report Metal available. These public source files execute
32
+ locally during preparation/training; inspect the scripts before running them.
33
+
34
+ Preparation requests 128 eligible Scenario meshes plus 128 CC0 Objaverse meshes,
35
+ four views each. It excludes known v1 asset IDs, paths, source groups, and exact
36
+ mesh hashes. It keeps every view of each related object in the same group. A
37
+ separate 15 percent test split is frozen before training. Validation uses 20
38
+ percent of the remaining development groups. Counts depend on duplicate groups;
39
+ read `data/forma3d-v2/preparation.json` and `v2-audit.json` for the realized counts.
40
+
41
+ The mesh-download budget is 4,096 MiB per preparation invocation, excluding dataset
42
+ metadata and Python packages. It reserves 8 GiB of free disk while fetching meshes.
43
+ Have comfortably more than 12 GiB available before setup and preparation; this is
44
+ a planning allowance, not a measured total. RAM and duration have not been
45
+ benchmarked for this exact v2 recipe. No pretrained teacher is loaded.
46
+
47
+ Training initializes from **v1 model.pt**, rather than its original ellipsoid
48
+ checkpoint. It uses fresh AdamW, batch size 2, learning rate 0.00005, 8,000 optimizer
49
+ steps, weight decay 0.0001, gradient clipping at norm 1, seed 43, and validation
50
+ every 200 steps. This lower learning rate is a proposed conservative setting, not
51
+ a measured optimum. The best validation loss selects `best.pt`.
52
+
53
+ The underlying training command is:
54
+
55
+ ```bash
56
+ PY="$HOME/Library/Application Support/Forma3D-tiny-2/venv/bin/python"
57
+ caffeinate -i "$PY" -m image3d_student.cli train \
58
+ --manifest data/forma3d-v2/manifest.jsonl \
59
+ --init-from upstream/model.pt \
60
+ --out runs/forma3d-v2 --device mps --width 64 \
61
+ --steps 8000 --batch-size 2 --lr 0.00005 \
62
+ --eval-every 200 --validation-fraction 0.2 --seed 43
63
+ ```
64
+
65
+ Do not substitute grid 32 in this recipe: it increases query cells eightfold and
66
+ changes the evaluation representation. A width change is incompatible with the
67
+ starting learned weights.
68
+
69
+ ## Inspect and optionally publish
70
+
71
+ Evaluation compares v1 and the selected v2 checkpoint on **the same new test
72
+ objects**, requiring lower test loss and higher test occupancy IoU, plus lower
73
+ validation loss, a completed run, source separation, and nonempty qualitative
74
+ predictions. It writes local comparisons to `runs/forma3d-v2/inspection/` and
75
+ stages `release/forma3d-tiny-2/` only if the gates pass. Inspect these PNGs.
76
+ The release card uses the actual run measurements and correctly records v1 as
77
+ its parent. No scores are prefilled.
78
+
79
+ The test set is for final assessment. If a release gate fails, do not repeatedly
80
+ tune hyperparameters against that test set. Develop on validation data and reserve
81
+ another genuinely unused object set for a later final claim.
82
+
83
+ To publish after inspection:
84
+
85
+ ```bash
86
+ "$HOME/Library/Application Support/Forma3D-tiny-2/venv/bin/hf" auth login
87
+ bash posttrain.sh publish
88
+ ```
89
+
90
+ Use your own Hugging Face write token in Terminal, not chat. This command creates
91
+ `Lucasgitguy-co/forma3d-tiny-2` privately, uploads the evaluated package, and makes
92
+ it public after the upload succeeds. It refuses to overwrite an existing repo.
93
+ A failed upload may leave a private partial repo; inspect that repo before
94
+ attempting recovery. Only the staged release folder is uploaded. Raw meshes,
95
+ download caches, credentials, and optimizer state are excluded.
96
+
97
+ ## Generate a Gaussian PLY
98
+
99
+ Use a centered object cutout on black. Replace the example path with your image:
100
+
101
+ ```bash
102
+ PY="$HOME/Library/Application Support/Forma3D-tiny-2/venv/bin/python"
103
+ "$PY" -m image3d_student.cli generate \
104
+ --checkpoint runs/forma3d-v2/best.pt --image /path/to/your-cutout.png \
105
+ --out results/object.ply --device mps
106
+ "$PY" -m image3d_student.cli preview \
107
+ --ply results/object.ply --out results/preview.png
108
+ ```
109
+
110
+ This model outputs Gaussian PLY, not a GLB or triangle mesh. It has no text
111
+ conditioner, automatic background remover, or verified natural-photo training.
112
+ The training images are renders of mesh-derived Gaussian proxies. The metadata
113
+ flag `contains_synthetic: false` means no procedural smoke-test fixtures; it does
114
+ not mean the input images are photographs or that all upstream meshes are human
115
+ made. Scenario includes generated 3D assets.
116
+
117
+ ## Recovery and adjustments
118
+
119
+ Dataset preparation can resume: repeat `bash posttrain.sh prepare`. If there are
120
+ not enough eligible assets, edit `--objaverse-shards 16` to 32 in `posttrain.sh`
121
+ and repeat preparation; the upstream parser permits up to 160. If the download
122
+ budget is exhausted, the completed objects remain saved; rerun or increase the
123
+ budget after checking free space. Budgets apply per invocation.
124
+
125
+ Training requires an empty run directory. Ctrl-C saves completed steps to
126
+ `last.pt`, but the trainer does not implement exact optimizer-state resume.
127
+ The provided release pipeline intentionally expects initialization from pinned
128
+ v1, so a run restarted from an interrupted v2 checkpoint requires a separately
129
+ implemented evaluation/release path and is not supported here. Preserve the old
130
+ run and start a new output directory for a fresh attempt.
131
+
132
+ For memory pressure, close other large apps and use batch size 1 in a **fresh**
133
+ run. Keep training/evaluation devices consistent. Do not disable the MPS memory
134
+ limit. Do not promise a training duration: the fanless M4 Air may slow under load.
135
+ If evaluation fails, its diagnostic report remains in the run directory and
136
+ nothing is uploaded. Every evaluation staging attempt needs a fresh release path.
137
+
138
+ ## Files and validation
139
+
140
+ `prepare_v2.py` wraps the pinned upstream preprocessing without changing its
141
+ renderer or labels. `audit_data.py` checks known v1 overlap, cache hashes, and
142
+ split separation. `stage_v2.py` reuses upstream evaluation and writes correct v2
143
+ provenance. `publish_v2.py` verifies a complete SHA256 inventory before upload.
144
+
145
+ The kit was syntax-checked; its exclusion and data-audit logic was tested with
146
+ small local fixtures. Flag compatibility was checked against the pinned parser.
147
+ The full dataset download, MPS training, and Hugging Face publication were not
148
+ executed. This is a runnable recipe requiring validation on your Mac, not a
149
+ completed training result.
150
+
151
+ ## Primary sources
152
+
153
+ - [Pinned model and code](https://huggingface.co/Lucasgitguy-co/forma3d-tiny/tree/e80b8d895cc2e405894473abe4ce40b3472eaca7)
154
+ - [Original model commands](https://huggingface.co/Lucasgitguy-co/forma3d-tiny/blob/e80b8d895cc2e405894473abe4ce40b3472eaca7/COMMANDS.md)
155
+ - [Scenario dataset](https://huggingface.co/datasets/scenario-labs/3d-collection/tree/55b4c002b4c1529fc48da3dff98516c3069bfa3f)
156
+ - [Objaverse dataset](https://huggingface.co/datasets/allenai/objaverse/tree/556637099bf4fa79ea7b239d0d9c328b8a2e9ac8)
157
+
158
+ Preserve per-asset attributions. This package uses the existing Apache 2.0
159
+ implementation. Its source/data filtering additions are provided under Apache
160
+ 2.0; the original NOTICE and third-party/data license terms still apply.
DATASETS.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data selection and attribution
2
+
3
+ Reviewed 2026-11-02. User requested commercial-use-compatible data and exclusion
4
+ of noncommercial datasets. This recipe uses only the sources explicitly marked
5
+ included below. Repository-level labels do not replace individual asset terms.
6
+
7
+ | Source | Status | Reason |
8
+ |---|---|---|
9
+ | [scenario-labs/3d-collection](https://huggingface.co/datasets/scenario-labs/3d-collection) | Included, bounded GLB subset | Explicit CC-BY-4.0 row filter; Hunyuan-labeled models excluded; checksums verified against metadata. Attribution: Scenario; modifications: normalization, surface sampling, Gaussian proxy rendering. |
10
+ | [scenario-labs/3d-collection-hunyuan](https://huggingface.co/datasets/scenario-labs/3d-collection-hunyuan) | Excluded | Dataset expressly prohibits using assets to train or improve AI models. |
11
+ | [3dlg-hcvc/MultiScan](https://huggingface.co/datasets/3dlg-hcvc/MultiScan) | Excluded | CC-BY-NC-4.0, gated scene data. |
12
+ | [huanngzh/3D-Front](https://huggingface.co/datasets/huanngzh/3D-Front) | Excluded | CC-BY-NC-4.0, indoor scenes rather than directly aligned object targets. |
13
+ | [facebook/show3d-dataset](https://huggingface.co/datasets/facebook/show3d-dataset) | Excluded | CC-BY-NC-4.0; interaction/video data needs a separate task-specific pipeline. |
14
+ | [Voxel51/gaussian_splatting](https://huggingface.co/datasets/Voxel51/gaussian_splatting) | Deferred; not used | Card labels Apache-2.0 and describes a few scene reconstructions. This object-centered recipe does not establish camera alignment, object crops, or source-scene rights from the card alone. No importer or download is provided. |
15
+ | [allenai/objaverse](https://huggingface.co/datasets/allenai/objaverse) | Added: CC0 assets only | Dataset compilation is ODC-By; assets have separate licenses. Script accepts only individual `cc0` metadata entries, retains author/title/source links, and excludes animated assets. |
16
+ | [allenai/objaverse-xl](https://huggingface.co/datasets/allenai/objaverse-xl) | Future candidate; not used | Heterogeneous per-file licensing; no blanket commercial-use assumption or importer in this kit. |
17
+
18
+ Pinned revisions:
19
+
20
+ - Scenario: `55b4c002b4c1529fc48da3dff98516c3069bfa3f`
21
+ - Objaverse: `556637099bf4fa79ea7b239d0d9c328b8a2e9ac8`
22
+ - Original model: `1738cdfaf1bb940bf576a96d11b742ce014b9139`
23
+
24
+ The actual asset inventory is generated as `sources.json`, with asset IDs, source
25
+ URLs, authors, licenses, revision IDs and SHA-256 hashes. Published releases copy
26
+ this file to `ATTRIBUTIONS.json`. Keep it with the released model. Rejected assets
27
+ are recorded in `skipped.json`. The model card must not list excluded or merely
28
+ considered datasets as training sources.
29
+
30
+ License references: [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/),
31
+ [CC0](https://creativecommons.org/publicdomain/zero/1.0/),
32
+ [ODC-By](https://opendatacommons.org/licenses/by/1-0/).
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Forma3D-Tiny v0.2.0, derived from Image3D Student v0.1.0
2
+ Copyright 2026 Lucasgitguy-co.
3
+
4
+ Original project source, documentation and procedural demonstration artifacts are released under Apache License 2.0.
5
+
6
+ The optional Idol integration and Gaussian attribute conventions were developed with reference to
7
+ speridlabs/idol-sdk, revision 4702a7f00850e34b6e83a2170cc123c3e9570744 (Apache-2.0).
8
+ Its architecture is imported as an optional dependency; pretrained teacher weights are not redistributed.
9
+ Adaptations in this project: staged loading, query-chunk attention, explicit real-valued RoPE selection,
10
+ independent student training, coordinate-preserving Gaussian IO, and diagnostic rendering.
11
+ Upstream attribution is retained in licenses/Idol-APACHE-2.0.txt and THIRD_PARTY.md.
README.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: image-to-3d
4
+ base_model: Lucasgitguy-co/forma3d-tiny
5
+ base_model_relation: finetune
6
+ model_name: Forma3D Tiny 2
7
+ datasets:
8
+ - allenai/objaverse
9
+ - scenario-labs/3d-collection
10
+ tags:
11
+ - pytorch
12
+ - gaussian-splatting
13
+ - mesh-proxy-supervision
14
+ - research-prototype
15
+ ---
16
+ # Forma3D Tiny 2
17
+
18
+ A 306,321-parameter image-to-Gaussian research model continued from
19
+ Lucasgitguy-co/forma3d-tiny at revision `e80b8d895cc2e405894473abe4ce40b3472eaca7`. The optimizer starts fresh. It uses a
20
+ 16 cubed grid and width 64.
21
+ Selected checkpoint step 7800 from 8000 completed steps.
22
+
23
+ ## Training
24
+
25
+ See training.json and metrics.jsonl for actual optimizer settings and validation
26
+ history. Inputs are orthographic renders of centered mesh-derived Gaussian proxy
27
+ clouds on black. Targets are isotropic surface samples compressed to one Gaussian
28
+ per occupied grid cell. No pretrained 3D teacher was run. No natural-photo training
29
+ or differentiable rendering is claimed. Asset IDs, known source groups, and exact
30
+ mesh hashes from all 96 known v1 assets were excluded. Near duplicates can remain.
31
+ The separate 15 percent object-group test split does not enter checkpoint selection;
32
+ 20 percent of development groups supply validation. Counts are realized counts in
33
+ preparation.json and data-audit.json, not assumed independent asset counts.
34
+
35
+ ## Evaluation
36
+
37
+ Both checkpoints were evaluated on the same new held-out proxy data.
38
+
39
+ | Metric | Starting Forma3D Tiny | Selected Forma3D Tiny 2 |
40
+ |---|---:|---:|
41
+ | Mean test loss | 0.616513 | 0.570490 |
42
+ | Mean view occupancy IoU at threshold 0.5 | 0.286327 | 0.327870 |
43
+
44
+ Test groups: 36; views: 156.
45
+ See evaluation.json for hashes and release gates. Metrics are view-averaged over
46
+ proxy renders. They do not establish photo realism, novel-view image quality,
47
+ mesh quality, texture fidelity, gameplay, or general-purpose 3D reconstruction.
48
+
49
+ ## Use
50
+
51
+ Install this source with Python 3.11 and `uv pip install ".[data]"` in a virtual
52
+ environment, then run:
53
+
54
+ ```bash
55
+ image3d generate --checkpoint model.pt --image your-centered-cutout.png \
56
+ --out results/object.ply --device mps
57
+ image3d preview --ply results/object.ply --out results/preview.png
58
+ ```
59
+
60
+ Output is Gaussian PLY. Inputs should match centered objects on black. See
61
+ COMMANDS.md for the complete continuation recipe. This model retains the same
62
+ architecture; no text-prompt conditioning or new mesh decoder was added.
63
+
64
+ ## License
65
+
66
+ Publisher source and weights are offered under Apache 2.0. Retain NOTICE,
67
+ THIRD_PARTY.md and ATTRIBUTIONS.json. CC BY Scenario assets and CC0 Objaverse
68
+ assets have their own attribution and license records in DATASETS.md.
SHA256SUMS ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 78da3c349a48a35a9377f255731816f6305b9f9bd6a4a1bd2e8558f730b235bc ATTRIBUTIONS.json
2
+ 65c732b729f360fc635af852cbf9a806cade0ddd39fb8796d438b93ce6afdedc COMMANDS.md
3
+ 4c09bfeb821d41b9da58c9a20c28c4000f40956f4359a04274556436a4ed009f DATASETS.md
4
+ cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
5
+ 05f38086626c05b43280173a453858ff2eb4273d63b554c94492c64f09a20ab3 NOTICE
6
+ 345b746672faf2fcc79e7936de113ef7c16ce3c2e4d65220c2c7e5a0aaafc083 README.md
7
+ ef7c689f184a7921b02887eeb8d859e8cca77da6d2082999bb19bf29beda61f1 THIRD_PARTY.md
8
+ 171d8c1bbcbdff4c7e1e30c139a38ee213de626c25efc94e109eed53a052eb3a audit_data.py
9
+ 25d53c6500d9e45aca8a2ce1fe54030e05a0822c554ab24cfcbfeaa631ee9c91 config.json
10
+ 91e4d34b8eb0ebffd9ba574780df886dd2e2a27fcc860d4456477e62a71a676e data-audit.json
11
+ c3bb8be792cef70bbb0f2db68a7008d63770ebaf935f79064157705b06630872 environment.txt
12
+ 27bdd414748a7e0ceccfce35b7f3c6dbc5398c5c35ef1bfc7f4538d0d437ada0 evaluation.json
13
+ cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 licenses/Idol-APACHE-2.0.txt
14
+ cabfa52ac7194eff926410d8c1d009eae46eb0baf5ec9d8702a16155d503d242 metrics.jsonl
15
+ c051b3f93d874f7c638588c9d0381754d3f954b1cd271121a033b2478d36bb1a model.pt
16
+ ac809ac48df5c9227cf88e723d3be0befd719902924254e577bf82622558994c posttrain.sh
17
+ 74f287ea799dbcad69fe61ad6bc176d890883a7ac0c1489a72ab8348d2947439 preparation.json
18
+ c4c25055a96becb3996885e204e9a70e94b706c1d5dd5e58bd2ade038f3e80cd prepare_v2.py
19
+ 5f87c57440379f6de332b75450c0a60e7e5c287f2ef4147cc69e1b77e70341ff publish_v2.py
20
+ 05b58f19668b5c7778ac9aa9d1aee03eb60771b9850a138533fda97498dd5e46 pyproject.toml
21
+ 53dc8f90461d899ed8c15574c3e96f9bf07da4d3ee783a47ec3a3b077b6ee9ca requirements-tested-macos.txt
22
+ 03325cb525f9e0562865f8df123678a147db38680b024f0845482dc2d5bc1651 scripts/prepare_hf.py
23
+ 96f7df49a214e05b86c2d86023d6500986a4d17b2dfe8b23ab7d213d75375fad scripts/release.py
24
+ c09ce8cb78cd68ae32b618ba49f97508b6ce14bc89e88c5b80cc3e2487bd919c src/image3d_student/__init__.py
25
+ b4be53a864458a84009e91277ff64767dffc6139ca3afcc50526747743722c02 src/image3d_student/cli.py
26
+ d34f9fa50d574764e5e2c1c31c1ca477f2bc4a1887e103c5c47b754f1f1d8c72 src/image3d_student/data.py
27
+ e01d1b42929c993d92cad1bde8944b14c6a8e325fc279d8fd3e9c7287523ad69 src/image3d_student/demo.py
28
+ 7335dcc99fcd9cea5c217841376f0ca8c0ca179809b4df34e344bf929d11da51 src/image3d_student/engine.py
29
+ a18005f75a66846446b2574b4f1b085ebde5321e835c4e5b4507e15e812ee5ef src/image3d_student/model.py
30
+ 41961fae62d2819c1e3f455e74539a66b8c4f57bedd9fb9379497b3a5764c692 src/image3d_student/splats.py
31
+ 2e2439bc0a48c52e32a8e273f879ed7328fdb82632a996ed3e239e0391ca3a59 src/image3d_student/teacher_idol.py
32
+ 78f2c8dbf24c7b2eaba4bc203e7b15e2fd8d043c5509b788bd1565773d31478a stage_v2.py
33
+ 0148fd204dd45f37ed53746859cf1d953ac49d16aa9cf3bcd77946a4c4f8c613 training.json
THIRD_PARTY.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Third-party scope
2
+
3
+ This training kit extends Lucasgitguy-co/image3d-student, Apache-2.0. The original
4
+ checkpoint is downloaded separately. No new trained weights are bundled in the kit.
5
+
6
+ Optional Idol SDK integration is inherited from the original project, pinned to
7
+ `4702a7f00850e34b6e83a2170cc123c3e9570744`; its Apache notice is retained in
8
+ `licenses/Idol-APACHE-2.0.txt`. The Mac recipe does not install or run Idol, Hi3D,
9
+ InstantMesh, Hunyuan, or gated encoders. Their weights are not redistributed.
10
+
11
+ Dependencies retain their respective licenses. Third-party dataset terms are
12
+ documented in DATASETS.md; per-asset attribution is generated during preparation
13
+ and copied into any published release. Code licensing does not relicense datasets.
audit_data.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Check published source identities plus split and exact-byte separation."""
2
+ import hashlib
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def identity_sets(rows):
9
+ result = {'asset': set(), 'path': set(), 'group': set(), 'sha256': set()}
10
+ for r in rows:
11
+ repo = r['repo']
12
+ if r.get('asset_id'): result['asset'].add((repo, r['asset_id']))
13
+ if r.get('path'): result['path'].add((repo, r['path']))
14
+ for key in ('object_id', 'source_object_id'):
15
+ if r.get(key): result['group'].add((repo, r[key]))
16
+ if r.get('sha256'): result['sha256'].add(r['sha256'])
17
+ return result
18
+
19
+
20
+ def conflicts(row, blocked):
21
+ current = identity_sets([row])
22
+ return any(current[k] & blocked[k] for k in blocked)
23
+
24
+
25
+ def audit(data, original):
26
+ data, original = Path(data), Path(original)
27
+ old = json.loads(original.read_text())
28
+ new = json.loads((data / 'sources.json').read_text())
29
+ blocked = identity_sets(old)
30
+ if any(conflicts(r, blocked) for r in new):
31
+ raise ValueError('v1 identity or mesh-byte overlap detected')
32
+ dev, test = [[json.loads(s) for s in (data / f).read_text().splitlines() if s.strip()]
33
+ for f in ('manifest.jsonl', 'test-manifest.jsonl')]
34
+ if not dev or not test:
35
+ raise ValueError('Both development and test data must be nonempty')
36
+ for rows in (dev, test):
37
+ for row in rows:
38
+ if conflicts(row['metadata'], blocked):
39
+ raise ValueError('Manifest includes a known v1 source')
40
+ for kind in ('image', 'target'):
41
+ p = (data / row[kind]).resolve()
42
+ if not p.is_relative_to(data.resolve()):
43
+ raise ValueError('Manifest path escapes dataset directory')
44
+ if hashlib.sha256(p.read_bytes()).hexdigest() != row[kind + '_sha256']:
45
+ raise ValueError('Cached file checksum mismatch')
46
+ for getter in (lambda r:r['metadata']['object_id'], lambda r:r['image_sha256'], lambda r:r['target_sha256']):
47
+ if {getter(r) for r in dev} & {getter(r) for r in test}:
48
+ raise ValueError('Development/test overlap detected')
49
+ report = {'known_v1_sources_excluded': len(old), 'new_mesh_assets':len(new),
50
+ 'development_samples':len(dev), 'test_samples':len(test),
51
+ 'test_object_groups':len({r['metadata']['object_id'] for r in test}),
52
+ 'checks':'source IDs, groups, source hashes, cache hashes, split groups and exact bytes',
53
+ 'limitation':'Semantic near duplicates and unknown upstream overlap can remain.'}
54
+ (data / 'v2-audit.json').write_text(json.dumps(report, indent=2))
55
+ print(json.dumps(report, indent=2))
56
+ return report
57
+
58
+ if __name__ == '__main__':
59
+ audit(*sys.argv[1:])
config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "grid": 16,
3
+ "width": 64
4
+ }
data-audit.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "known_v1_sources_excluded": 96,
3
+ "new_mesh_assets": 256,
4
+ "development_samples": 868,
5
+ "test_samples": 156,
6
+ "test_object_groups": 36,
7
+ "checks": "source IDs, groups, source hashes, cache hashes, split groups and exact bytes",
8
+ "limitation": "Semantic near duplicates and unknown upstream overlap can remain."
9
+ }
environment.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio==4.16.0
2
+ click==8.5.0
3
+ filelock==4.2.7
4
+ fsspec==2026.9.0
5
+ h11==0.16.0
6
+ hf-xet==1.8.0
7
+ httpcore2==2.16.0
8
+ httpx2==2.16.0
9
+ huggingface-hub==2.4.0
10
+ idna==3.20
11
+ iniconfig==2.3.1
12
+ jinja2==3.1.6
13
+ markupsafe==3.0.4
14
+ mpmath==1.3.0
15
+ networkx==3.6.1
16
+ numpy==2.4.6
17
+ packaging==26.3
18
+ pillow==13.0.0
19
+ pluggy==1.7.0
20
+ plyfile==1.1.5
21
+ pyarrow==26.0.0
22
+ pygments==2.21.0
23
+ pytest==9.2.0
24
+ pyyaml==6.0.3
25
+ scipy==1.17.1
26
+ setuptools==84.2.1
27
+ sympy==1.14.0
28
+ torch==2.15.0
29
+ tqdm==4.70.1
30
+ trimesh==5.2.0
31
+ truststore==0.10.5
32
+ typing-extensions==4.16.0
33
+ # Local forma3d-tiny 0.2.0 source: Lucasgitguy-co/forma3d-tiny @ e80b8d895cc2e405894473abe4ce40b3472eaca7
evaluation.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "checkpoint_sha256": "e0fd24520297a11102412ba8cf5a0fcc1f1103100368d0a438ca63507f581665",
3
+ "base_sha256": "83c065d2a50cf086928542cf8d40dc4416b379e38b9aa61d842dc9106d4b3306",
4
+ "test_manifest_sha256": "5d93ad4fb6c0b9047d06aeae0bd8df76bedcbb3a591d39e00f112c2d7cabfcb1",
5
+ "test_objects": 36,
6
+ "test_samples": 156,
7
+ "baseline_test": {
8
+ "occupancy": 0.45381076557513994,
9
+ "position": 0.09375727468003066,
10
+ "colour": 0.03423368351128048,
11
+ "scale": 0.0023920839437498497,
12
+ "rotation": 9.140115868761383e-05,
13
+ "opacity": 0.00022972543981781293,
14
+ "geometry": 0.0,
15
+ "loss": 0.6165134836083803,
16
+ "occupancy_iou": 0.28632665831500137
17
+ },
18
+ "posttrained_test": {
19
+ "occupancy": 0.4105527498401128,
20
+ "position": 0.093269259071885,
21
+ "colour": 0.033224766163561396,
22
+ "scale": 0.001095225648150051,
23
+ "rotation": 5.7498695502416895e-06,
24
+ "opacity": 0.00010812017677754062,
25
+ "geometry": 0.0,
26
+ "loss": 0.5704897603927515,
27
+ "occupancy_iou": 0.32786990109926617
28
+ },
29
+ "checks": {
30
+ "validation_loss_improved": true,
31
+ "test_loss_improved": true,
32
+ "test_occupancy_iou_improved": true,
33
+ "test_occupancy_iou_at_least_005": true
34
+ },
35
+ "passed": true,
36
+ "scope": "Rendered mesh-proxy evaluation only; does not establish real-photo or mesh reconstruction quality",
37
+ "base_model": "Lucasgitguy-co/forma3d-tiny",
38
+ "base_revision": "e80b8d895cc2e405894473abe4ce40b3472eaca7",
39
+ "published_model_sha256": "c051b3f93d874f7c638588c9d0381754d3f954b1cd271121a033b2478d36bb1a"
40
+ }
licenses/Idol-APACHE-2.0.txt ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
metrics.jsonl ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"step": 1, "seconds": 2.226, "train_loss": 0.7118504047393799, "occupancy": 0.5707302093505859, "position": 0.09329640120267868, "colour": 0.02375972457230091, "scale": 0.00139362714253366, "rotation": 0.00013228011084720492, "opacity": 0.00015177681052591652, "geometry": 0.0, "validation": {"occupancy": 0.4518862460222509, "position": 0.0943518355074856, "colour": 0.033528136679928544, "scale": 0.00237332220422104, "rotation": 0.00010222768609714371, "opacity": 0.000220453495194306, "geometry": 0.0, "loss": 0.6137623639570342, "occupancy_iou": 0.2452177435159683}}
2
+ {"step": 200, "seconds": 9.907, "train_loss": 0.6102267503738403, "occupancy": 0.3629882037639618, "position": 0.1010059267282486, "colour": 0.07303024083375931, "scale": 0.001368745812214911, "rotation": 3.1407173082698137e-05, "opacity": 3.210359864169732e-05, "geometry": 0.0, "validation": {"occupancy": 0.4392792442606555, "position": 0.09434974806176291, "colour": 0.03353508860131519, "scale": 0.0019467268908758544, "rotation": 7.532406785281233e-05, "opacity": 0.0001515494514706006, "geometry": 0.0, "loss": 0.6010529238316747, "occupancy_iou": 0.2503086172665159}}
3
+ {"step": 400, "seconds": 17.151, "train_loss": 0.36874061822891235, "occupancy": 0.23984085023403168, "position": 0.09525543451309204, "colour": 0.016660282388329506, "scale": 0.0022550125140696764, "rotation": 5.368763959268108e-05, "opacity": 9.286287968279794e-05, "geometry": 0.0, "validation": {"occupancy": 0.4414640225470066, "position": 0.09427301647762458, "colour": 0.03249235675450311, "scale": 0.0021028166339318788, "rotation": 5.6945074523658455e-05, "opacity": 0.00013960947958366888, "geometry": 0.0, "loss": 0.6010773383908802, "occupancy_iou": 0.24863199333970745}}
4
+ {"step": 600, "seconds": 26.22, "train_loss": 0.6636115312576294, "occupancy": 0.557167649269104, "position": 0.09432454407215118, "colour": 0.005989818833768368, "scale": 0.0010824797209352255, "rotation": 2.2256899683270603e-05, "opacity": 2.9281885872478597e-05, "geometry": 0.0, "validation": {"occupancy": 0.44066500614086784, "position": 0.09423391732076804, "colour": 0.03306895266545729, "scale": 0.0017604425968278923, "rotation": 5.2143132658481285e-05, "opacity": 0.00013570198060885256, "geometry": 0.0, "loss": 0.6013537890381283, "occupancy_iou": 0.26753482399508355}}
5
+ {"step": 800, "seconds": 33.596, "train_loss": 0.709161102771759, "occupancy": 0.5743151307106018, "position": 0.09445250779390335, "colour": 0.019850170239806175, "scale": 0.004002443514764309, "rotation": 3.6538607673719525e-05, "opacity": 0.0002892622142098844, "geometry": 0.0, "validation": {"occupancy": 0.4343525725106398, "position": 0.09426615482403172, "colour": 0.031267428182117224, "scale": 0.0022783598438965986, "rotation": 5.059475087768482e-05, "opacity": 0.00017218820794874369, "geometry": 0.0, "loss": 0.5915586706664827, "occupancy_iou": 0.25203666733577845}}
6
+ {"step": 1000, "seconds": 39.104, "train_loss": 0.478383868932724, "occupancy": 0.3632516860961914, "position": 0.0865977555513382, "colour": 0.014173795469105244, "scale": 0.00096982711693272, "rotation": 5.3548519645119086e-05, "opacity": 8.451077155768871e-05, "geometry": 0.0, "validation": {"occupancy": 0.42965368785791924, "position": 0.09419222561021647, "colour": 0.03181575125264418, "scale": 0.0019403017205250864, "rotation": 4.755257740119608e-05, "opacity": 0.00015420117577680988, "geometry": 0.0, "loss": 0.5878304019570351, "occupancy_iou": 0.2599639593209657}}
7
+ {"step": 1200, "seconds": 43.034, "train_loss": 0.3406582474708557, "occupancy": 0.18636450171470642, "position": 0.09451016783714294, "colour": 0.02972484566271305, "scale": 0.0024863339494913816, "rotation": 2.946602580777835e-05, "opacity": 8.229353261413053e-05, "geometry": 0.0, "validation": {"occupancy": 0.42523774239752027, "position": 0.09409754392173555, "colour": 0.03096643993986719, "scale": 0.0020236761727978267, "rotation": 4.635460904864885e-05, "opacity": 0.0002172642122205717, "geometry": 0.0, "loss": 0.5816924356751971, "occupancy_iou": 0.25514045654692585}}
8
+ {"step": 1400, "seconds": 46.627, "train_loss": 0.5402199029922485, "occupancy": 0.40528902411460876, "position": 0.09599009901285172, "colour": 0.01943030022084713, "scale": 0.0004655070079024881, "rotation": 1.4336257663671859e-05, "opacity": 3.219417340005748e-05, "geometry": 0.0, "validation": {"occupancy": 0.4270147377418147, "position": 0.09427343706289927, "colour": 0.0308504894980514, "scale": 0.0019938124537778397, "rotation": 3.838655797052424e-05, "opacity": 0.00015617236028649334, "geometry": 0.0, "loss": 0.5833485422862901, "occupancy_iou": 0.2615635176499685}}
9
+ {"step": 1600, "seconds": 50.953, "train_loss": 0.4305267035961151, "occupancy": 0.31603896617889404, "position": 0.08737245947122574, "colour": 0.013378691859543324, "scale": 0.0014701660256832838, "rotation": 1.3187261174607556e-05, "opacity": 0.0002095560048473999, "geometry": 0.0, "validation": {"occupancy": 0.4317949041724205, "position": 0.09412531273232566, "colour": 0.03117768721001792, "scale": 0.0017864460874180723, "rotation": 3.7251114496130966e-05, "opacity": 0.00014699193042259947, "geometry": 0.0, "loss": 0.5886049575275845, "occupancy_iou": 0.25071487617161536}}
10
+ {"step": 1800, "seconds": 54.626, "train_loss": 0.6337854266166687, "occupancy": 0.45424965023994446, "position": 0.09028906375169754, "colour": 0.044515978544950485, "scale": 0.0011866714339703321, "rotation": 4.060083665535785e-05, "opacity": 9.203815716318786e-05, "geometry": 0.0, "validation": {"occupancy": 0.42853521431485814, "position": 0.09408483128580782, "colour": 0.030684983537907506, "scale": 0.0018963307412276562, "rotation": 3.8867297088371643e-05, "opacity": 0.00013767477012152085, "geometry": 0.0, "loss": 0.5843212089604801, "occupancy_iou": 0.26198453576200537}}
11
+ {"step": 2000, "seconds": 58.299, "train_loss": 0.4911178946495056, "occupancy": 0.3473135232925415, "position": 0.09329692274332047, "colour": 0.02516920119524002, "scale": 0.0009039711439982057, "rotation": 1.2793458154192194e-05, "opacity": 7.736129919067025e-05, "geometry": 0.0, "validation": {"occupancy": 0.4308644436299801, "position": 0.09404570534825325, "colour": 0.03126794581443796, "scale": 0.001913158190695362, "rotation": 3.8796406690582645e-05, "opacity": 0.000161123547008578, "geometry": 0.0, "loss": 0.5878023596273528, "occupancy_iou": 0.26729344308583275}}
12
+ {"step": 2200, "seconds": 62.423, "train_loss": 0.49473559856414795, "occupancy": 0.39490997791290283, "position": 0.09424534440040588, "colour": 0.0026967048179358244, "scale": 0.001267485087737441, "rotation": 2.2478219761978835e-05, "opacity": 5.78755461901892e-05, "geometry": -0.0, "validation": {"occupancy": 0.42324845335549777, "position": 0.09407929811212752, "colour": 0.030908581189255022, "scale": 0.001734178906190209, "rotation": 2.9401834669645017e-05, "opacity": 0.00013252917611781515, "geometry": 0.0, "loss": 0.5794538059168391, "occupancy_iou": 0.26256160982367066}}
13
+ {"step": 2400, "seconds": 66.234, "train_loss": 0.5510505437850952, "occupancy": 0.3978094458580017, "position": 0.09358540922403336, "colour": 0.029724668711423874, "scale": 0.001308542676270008, "rotation": 2.4227834728662856e-05, "opacity": 7.308263593586162e-05, "geometry": 0.0, "validation": {"occupancy": 0.4158506722913848, "position": 0.0939772955659363, "colour": 0.03368225522701525, "scale": 0.0016470129029282057, "rotation": 3.0246070622928934e-05, "opacity": 0.0001484720899245278, "geometry": 0.0, "loss": 0.5775086745619774, "occupancy_iou": 0.26686940054512687}}
14
+ {"step": 2600, "seconds": 69.678, "train_loss": 0.5078648924827576, "occupancy": 0.4009518027305603, "position": 0.09459930658340454, "colour": 0.006019631866365671, "scale": 0.00107279303483665, "rotation": 2.1116564312251285e-05, "opacity": 0.00016517315816599876, "geometry": 0.0, "validation": {"occupancy": 0.4215878984166516, "position": 0.0941099154866404, "colour": 0.030351898755503093, "scale": 0.002087128064886201, "rotation": 3.372074203424644e-05, "opacity": 0.0001546000525498433, "geometry": 0.0, "loss": 0.5767682999372482, "occupancy_iou": 0.26642631568635505}}
15
+ {"step": 2800, "seconds": 73.936, "train_loss": 0.5524424314498901, "occupancy": 0.45049646496772766, "position": 0.08658759295940399, "colour": 0.00761753274127841, "scale": 0.0006960614700801671, "rotation": 2.2003196136211045e-05, "opacity": 5.14989260409493e-05, "geometry": 0.0, "validation": {"occupancy": 0.41681441391507784, "position": 0.09399882223871019, "colour": 0.030614230668612033, "scale": 0.0018567735597672355, "rotation": 2.7371126323790427e-05, "opacity": 0.0001512159914960244, "geometry": 0.0, "loss": 0.5723813250660896, "occupancy_iou": 0.2721572133091589}}
16
+ {"step": 3000, "seconds": 77.85, "train_loss": 0.47857388854026794, "occupancy": 0.32654982805252075, "position": 0.0985688716173172, "colour": 0.026567257940769196, "scale": 0.0018755781929939985, "rotation": 1.0815320820256602e-05, "opacity": 0.00013207261508796364, "geometry": 0.0, "validation": {"occupancy": 0.4110039444433318, "position": 0.09397411880393823, "colour": 0.03051911240812236, "scale": 0.0018324703995151342, "rotation": 2.6987933491303314e-05, "opacity": 0.00015093530021456698, "geometry": 0.0, "loss": 0.5663531646132469, "occupancy_iou": 0.26360250164030324}}
17
+ {"step": 3200, "seconds": 82.182, "train_loss": 0.5254156589508057, "occupancy": 0.3551676273345947, "position": 0.09234742075204849, "colour": 0.038872893899679184, "scale": 0.0010524942772462964, "rotation": 1.719896317808889e-05, "opacity": 4.7851517592789605e-05, "geometry": 0.0, "validation": {"occupancy": 0.4228256072435114, "position": 0.09399812126325237, "colour": 0.0307030633609328, "scale": 0.0016338224047406887, "rotation": 2.9768100080218574e-05, "opacity": 0.0001273464747100661, "geometry": 0.0, "loss": 0.5785235651665264, "occupancy_iou": 0.2846426756017738}}
18
+ {"step": 3400, "seconds": 87.183, "train_loss": 0.4364839792251587, "occupancy": 0.3158171772956848, "position": 0.09616897255182266, "colour": 0.012188517488539219, "scale": 0.0007229623151943088, "rotation": 1.076408716471633e-05, "opacity": 4.740383883472532e-05, "geometry": -0.0, "validation": {"occupancy": 0.4094659690227773, "position": 0.09397175216840373, "colour": 0.03179174260003492, "scale": 0.001720584945966645, "rotation": 2.1549060081977384e-05, "opacity": 0.00012737575575051273, "geometry": 0.0, "loss": 0.5673228002256817, "occupancy_iou": 0.2701680043194857}}
19
+ {"step": 3600, "seconds": 91.249, "train_loss": 0.49549779295921326, "occupancy": 0.34523046016693115, "position": 0.08855268359184265, "colour": 0.030767172574996948, "scale": 0.0006670482689514756, "rotation": 2.0991266865166835e-05, "opacity": 0.00011153484228998423, "geometry": 0.0, "validation": {"occupancy": 0.41149804004364543, "position": 0.09396016506685151, "colour": 0.031038793543120845, "scale": 0.0016648945602355525, "rotation": 2.195297413436492e-05, "opacity": 0.00015779983397502898, "geometry": 0.0, "loss": 0.5678622765673531, "occupancy_iou": 0.27745231499688494}}
20
+ {"step": 3800, "seconds": 95.935, "train_loss": 0.45338520407676697, "occupancy": 0.2673555910587311, "position": 0.09842601418495178, "colour": 0.043742645531892776, "scale": 0.0007127065327949822, "rotation": 1.5850951967877336e-05, "opacity": 4.546590207610279e-05, "geometry": 0.0, "validation": {"occupancy": 0.4185141497188144, "position": 0.09392823518978224, "colour": 0.030558659026994266, "scale": 0.0016994932399635825, "rotation": 2.1529511650442144e-05, "opacity": 0.0001690847278748859, "geometry": 0.0, "loss": 0.5739008857972092, "occupancy_iou": 0.28688534133964116}}
21
+ {"step": 4000, "seconds": 99.234, "train_loss": 0.557262122631073, "occupancy": 0.41419923305511475, "position": 0.1001129150390625, "colour": 0.021421322599053383, "scale": 0.0008524729055352509, "rotation": 8.493616405758075e-06, "opacity": 2.1267600459395908e-05, "geometry": 0.0, "validation": {"occupancy": 0.4045540635784467, "position": 0.09390666542781724, "colour": 0.030530943222887193, "scale": 0.0015784881295985542, "rotation": 1.953120323605617e-05, "opacity": 0.00014177847832696797, "geometry": 0.0, "loss": 0.5598241958353255, "occupancy_iou": 0.27434742067837053}}
22
+ {"step": 4200, "seconds": 103.185, "train_loss": 0.6259624361991882, "occupancy": 0.3919915556907654, "position": 0.09183152765035629, "colour": 0.07100241631269455, "scale": 0.0011714748106896877, "rotation": 1.2915494153276086e-05, "opacity": 1.6088246411527507e-05, "geometry": 0.0, "validation": {"occupancy": 0.40893695917394424, "position": 0.09394071011079683, "colour": 0.031102682888417298, "scale": 0.0016566178788909585, "rotation": 2.15832443422591e-05, "opacity": 0.0001230442787598602, "geometry": 0.0, "loss": 0.5653738988770379, "occupancy_iou": 0.2786702503139774}}
23
+ {"step": 4400, "seconds": 107.226, "train_loss": 0.6597180366516113, "occupancy": 0.4113847017288208, "position": 0.09409773349761963, "colour": 0.07705115526914597, "scale": 0.0007961074006743729, "rotation": 1.7088928871089593e-05, "opacity": 5.190773299545981e-05, "geometry": 0.0, "validation": {"occupancy": 0.40834645562701755, "position": 0.09393176059756014, "colour": 0.03059931747460117, "scale": 0.001580410228537706, "rotation": 1.9219402352316846e-05, "opacity": 0.00011282105082904713, "geometry": 0.0, "loss": 0.5637496335638894, "occupancy_iou": 0.28006390310409995}}
24
+ {"step": 4600, "seconds": 111.711, "train_loss": 0.469024121761322, "occupancy": 0.25593164563179016, "position": 0.09084726125001907, "colour": 0.06106134131550789, "scale": 0.0006797174573875964, "rotation": 2.7496407710714266e-05, "opacity": 5.18183151143603e-05, "geometry": -0.0, "validation": {"occupancy": 0.4052144769165251, "position": 0.09393522159920799, "colour": 0.03133135766385951, "scale": 0.0014389909564569178, "rotation": 1.879761312137715e-05, "opacity": 0.00015053203554392288, "geometry": 0.0, "loss": 0.5621087267994881, "occupancy_iou": 0.2752341095565094}}
25
+ {"step": 4800, "seconds": 118.367, "train_loss": 0.4867999255657196, "occupancy": 0.3837716579437256, "position": 0.09228860586881638, "colour": 0.005275615490972996, "scale": 0.0010446624364703894, "rotation": 2.072399547614623e-05, "opacity": 8.185891783796251e-05, "geometry": 0.0, "validation": {"occupancy": 0.4076536825133695, "position": 0.09397915200226836, "colour": 0.030453578963836967, "scale": 0.0015870701287187532, "rotation": 1.7866719237089355e-05, "opacity": 0.00014790454023467546, "geometry": 0.0, "loss": 0.5628483845955796, "occupancy_iou": 0.2745954434387386}}
26
+ {"step": 5000, "seconds": 126.698, "train_loss": 0.5417315363883972, "occupancy": 0.3388543426990509, "position": 0.0951608270406723, "colour": 0.053798988461494446, "scale": 0.0008181341690942645, "rotation": 1.32617697090609e-05, "opacity": 3.5288030630908906e-05, "geometry": 0.0, "validation": {"occupancy": 0.40258289178212486, "position": 0.09389113494091564, "colour": 0.030977234217183045, "scale": 0.0015717818333844966, "rotation": 1.7154699538776994e-05, "opacity": 0.00012996606800596864, "geometry": 0.0, "loss": 0.5587173554632399, "occupancy_iou": 0.278588663517601}}
27
+ {"step": 5200, "seconds": 136.791, "train_loss": 0.4986310303211212, "occupancy": 0.39215320348739624, "position": 0.09362593293190002, "colour": 0.0063873776234686375, "scale": 0.0005547901382669806, "rotation": 1.3301656508701853e-05, "opacity": 2.0309231331339106e-05, "geometry": 0.0, "validation": {"occupancy": 0.40034629594948556, "position": 0.0939166457288795, "colour": 0.0316381377083922, "scale": 0.0013407457458217524, "rotation": 1.5355411326507036e-05, "opacity": 9.307003478069318e-05, "geometry": 0.0, "loss": 0.5577678953607877, "occupancy_iou": 0.28428437142736385}}
28
+ {"step": 5400, "seconds": 147.671, "train_loss": 0.5063523054122925, "occupancy": 0.37988996505737305, "position": 0.09676655381917953, "colour": 0.014790681190788746, "scale": 0.000719499948900193, "rotation": 1.0230924090137705e-05, "opacity": 4.14770474890247e-05, "geometry": 0.0, "validation": {"occupancy": 0.406855690644847, "position": 0.094014210999012, "colour": 0.030220710265954645, "scale": 0.0014052534357890383, "rotation": 1.3710945721514792e-05, "opacity": 0.00012029510183108667, "geometry": 0.0, "loss": 0.5615735145906607, "occupancy_iou": 0.2955626644194126}}
29
+ {"step": 5600, "seconds": 158.543, "train_loss": 0.4793046712875366, "occupancy": 0.31926438212394714, "position": 0.09948806464672089, "colour": 0.030066760256886482, "scale": 0.0016735820099711418, "rotation": 3.527296030370053e-06, "opacity": 0.00025098398327827454, "geometry": 0.0, "validation": {"occupancy": 0.4096036763654815, "position": 0.0940052620238728, "colour": 0.030777640448003594, "scale": 0.0017037645920128044, "rotation": 1.2175207258577655e-05, "opacity": 9.953662891146249e-05, "geometry": 0.0, "loss": 0.5654353524247805, "occupancy_iou": 0.29424606011145643}}
30
+ {"step": 5800, "seconds": 169.06, "train_loss": 0.5487256646156311, "occupancy": 0.4067179262638092, "position": 0.08838052302598953, "colour": 0.026750687509775162, "scale": 0.0008282989729195833, "rotation": 6.190408385009505e-06, "opacity": 4.235693268128671e-05, "geometry": 0.0, "validation": {"occupancy": 0.4043719614545504, "position": 0.0938253727224138, "colour": 0.03032789862126164, "scale": 0.0014254298906760394, "rotation": 1.2701534732665701e-05, "opacity": 0.0001225527391397918, "geometry": 0.0, "loss": 0.5591194975707267, "occupancy_iou": 0.28225652354045044}}
31
+ {"step": 6000, "seconds": 178.463, "train_loss": 0.7709605693817139, "occupancy": 0.6320652365684509, "position": 0.10362475365400314, "colour": 0.017524851486086845, "scale": 0.0013552490854635835, "rotation": 1.1179777175129857e-05, "opacity": 8.425069972872734e-05, "geometry": -0.0, "validation": {"occupancy": 0.40365004216631256, "position": 0.09382433742284775, "colour": 0.030227608712933337, "scale": 0.0013439785144227143, "rotation": 1.3150008344887789e-05, "opacity": 0.00011520726076115453, "geometry": 0.0, "loss": 0.5581805180344317, "occupancy_iou": 0.289635895875593}}
32
+ {"step": 6200, "seconds": 188.743, "train_loss": 0.4993472695350647, "occupancy": 0.3844979405403137, "position": 0.0935329720377922, "colour": 0.010608968324959278, "scale": 0.0006603781948797405, "rotation": 5.173298632143997e-06, "opacity": 3.185215246048756e-05, "geometry": 0.0, "validation": {"occupancy": 0.39319998257690003, "position": 0.093823934180869, "colour": 0.03010780170184767, "scale": 0.0013350714630279172, "rotation": 1.0495416969054835e-05, "opacity": 8.886457434932203e-05, "geometry": 0.0, "loss": 0.5474629404644171, "occupancy_iou": 0.28641757027556497}}
33
+ {"step": 6400, "seconds": 198.688, "train_loss": 0.4189801514148712, "occupancy": 0.2826651334762573, "position": 0.09773959964513779, "colour": 0.019192663952708244, "scale": 0.0007928699487820268, "rotation": 7.733145139354747e-06, "opacity": 0.00011002655810443684, "geometry": -0.0, "validation": {"occupancy": 0.4008911026020845, "position": 0.09377840674585766, "colour": 0.030930962648967074, "scale": 0.0013093957671016041, "rotation": 1.0876036795555894e-05, "opacity": 9.485433270128851e-05, "geometry": 0.0, "loss": 0.5567583110597398, "occupancy_iou": 0.2885369167766637}}
34
+ {"step": 6600, "seconds": 207.607, "train_loss": 0.5430406928062439, "occupancy": 0.40279972553253174, "position": 0.0946291834115982, "colour": 0.02274857833981514, "scale": 0.00046750501496717334, "rotation": 3.858339823636925e-06, "opacity": 6.750710599590093e-05, "geometry": 0.0, "validation": {"occupancy": 0.3953916298846404, "position": 0.09387033122281234, "colour": 0.029838704274799156, "scale": 0.001385571199514541, "rotation": 1.1168679466461779e-05, "opacity": 0.00012542731134696322, "geometry": 0.0, "loss": 0.549204474604792, "occupancy_iou": 0.29114852864295243}}
35
+ {"step": 6800, "seconds": 217.242, "train_loss": 0.42363783717155457, "occupancy": 0.3095293641090393, "position": 0.07285671681165695, "colour": 0.020542502403259277, "scale": 0.0011710914550349116, "rotation": 6.151235993456794e-06, "opacity": 4.898784391116351e-05, "geometry": 0.0, "validation": {"occupancy": 0.3905657473537657, "position": 0.09377354122698307, "colour": 0.030009023019293736, "scale": 0.0013380147245091697, "rotation": 9.336464271806714e-06, "opacity": 9.46656024098047e-05, "geometry": 0.0, "loss": 0.5445867335630787, "occupancy_iou": 0.28344639133041105}}
36
+ {"step": 7000, "seconds": 226.267, "train_loss": 0.3683210015296936, "occupancy": 0.2541470527648926, "position": 0.0920795351266861, "colour": 0.011000635102391243, "scale": 0.00044253666419535875, "rotation": 8.873310434864834e-06, "opacity": 4.801345494342968e-05, "geometry": -0.0, "validation": {"occupancy": 0.38817514338427117, "position": 0.09379746023979452, "colour": 0.030292692528908243, "scale": 0.0013068688216864958, "rotation": 9.695720117027425e-06, "opacity": 0.00010475461714476195, "geometry": 0.0, "loss": 0.5427944011158413, "occupancy_iou": 0.28759642725603446}}
37
+ {"step": 7200, "seconds": 236.128, "train_loss": 0.5596817135810852, "occupancy": 0.4352259635925293, "position": 0.09255465120077133, "colour": 0.015911228954792023, "scale": 0.0004941098159179091, "rotation": 5.525091637537116e-06, "opacity": 2.8721509806928225e-05, "geometry": 0.0, "validation": {"occupancy": 0.3937354901184638, "position": 0.09382040939397282, "colour": 0.029826522268639463, "scale": 0.0012667472010232611, "rotation": 9.296284631293223e-06, "opacity": 0.00012043797467716406, "geometry": 0.0, "loss": 0.5474569886922837, "occupancy_iou": 0.2878855074652367}}
38
+ {"step": 7400, "seconds": 245.65, "train_loss": 0.4626730978488922, "occupancy": 0.3224141299724579, "position": 0.09337978810071945, "colour": 0.02338295243680477, "scale": 0.0007951300358399749, "rotation": 3.5533946629584534e-06, "opacity": 3.340890907566063e-05, "geometry": 0.0, "validation": {"occupancy": 0.3877126969397068, "position": 0.0938801984820101, "colour": 0.030403074356986002, "scale": 0.00118190329085337, "rotation": 7.93976443813133e-06, "opacity": 9.667932798341402e-05, "geometry": 0.0, "loss": 0.5426147082613574, "occupancy_iou": 0.2788545132097271}}
39
+ {"step": 7600, "seconds": 255.315, "train_loss": 0.3988212049007416, "occupancy": 0.27856338024139404, "position": 0.0932641476392746, "colour": 0.013418144546449184, "scale": 0.0009028896456584334, "rotation": 3.5423447570792632e-06, "opacity": 6.672731979051605e-05, "geometry": 0.0, "validation": {"occupancy": 0.3907282780028052, "position": 0.09378029331564904, "colour": 0.03218639403607489, "scale": 0.0011547023844387797, "rotation": 8.038774109309087e-06, "opacity": 9.087624960102403e-05, "geometry": 0.0, "loss": 0.5490885093808174, "occupancy_iou": 0.29333951137959957}}
40
+ {"step": 7800, "seconds": 264.733, "train_loss": 0.43082183599472046, "occupancy": 0.3145463764667511, "position": 0.0849197506904602, "colour": 0.015606578439474106, "scale": 0.0010680370032787323, "rotation": 3.66301378562639e-06, "opacity": 3.537732845870778e-05, "geometry": -0.0, "validation": {"occupancy": 0.3866305548283789, "position": 0.09380314374963443, "colour": 0.02985135869692183, "scale": 0.0011766884298089685, "rotation": 7.77004361667524e-06, "opacity": 0.00013522217013309677, "geometry": 0.0, "loss": 0.5403900866707166, "occupancy_iou": 0.2947235658764839}}
41
+ {"step": 8000, "seconds": 274.229, "train_loss": 0.4784364402294159, "occupancy": 0.35019439458847046, "position": 0.09252085536718369, "colour": 0.01773099973797798, "scale": 0.0023379335179924965, "rotation": 4.9215291255677585e-06, "opacity": 2.4880051569198258e-05, "geometry": -0.0, "validation": {"occupancy": 0.3940945436557134, "position": 0.09373144254916244, "colour": 0.031111064605000946, "scale": 0.0012088158114541632, "rotation": 7.342030562540458e-06, "opacity": 9.256118964913184e-05, "geometry": 0.0, "loss": 0.5502622892459234, "occupancy_iou": 0.2887043085984058}}
model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c051b3f93d874f7c638588c9d0381754d3f954b1cd271121a033b2478d36bb1a
3
+ size 1753931
posttrain.sh ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Source and weights are pinned; no training or upload starts during setup.
3
+ set -euo pipefail
4
+ cd "$(dirname "$0")"
5
+ STAGE="${1:-help}"
6
+ RUNTIME="${FORMA3D_V2_RUNTIME:-$HOME/Library/Application Support/Forma3D-tiny-2/venv}"
7
+ PY="$RUNTIME/bin/python"
8
+ export HF_HUB_DISABLE_TELEMETRY=1
9
+ export OMP_NUM_THREADS=4
10
+ export VECLIB_MAXIMUM_THREADS=4
11
+ REV=e80b8d895cc2e405894473abe4ce40b3472eaca7
12
+ case "$STAGE" in
13
+ setup)
14
+ if ! command -v uv >/dev/null; then
15
+ echo 'Install uv first: brew install uv'; exit 1
16
+ fi
17
+ [ -x "$PY" ] || uv venv --python 3.11 "$RUNTIME"
18
+ uv pip install --python "$PY" 'huggingface_hub[cli]>=0.34'
19
+ "$RUNTIME/bin/hf" download Lucasgitguy-co/forma3d-tiny --revision "$REV" --local-dir upstream
20
+ uv pip install --python "$PY" -r upstream/requirements-tested-macos.txt
21
+ uv pip install --python "$PY" './upstream[data]'
22
+ "$PY" -m image3d_student.cli doctor
23
+ "$PY" -c 'import torch; assert torch.backends.mps.is_available(), "Metal unavailable; run in macOS Terminal"'
24
+ mkdir -p runs
25
+ uv pip freeze --python "$PY" > runs/environment.txt
26
+ ;;
27
+ prepare)
28
+ [ -x "$PY" ] || { echo 'Run bash posttrain.sh setup first'; exit 1; }
29
+ "$PY" prepare_v2.py --out data/forma3d-v2 --cache data/downloads \
30
+ --scenario 128 --objaverse 128 --objaverse-shards 16 --resume \
31
+ --grid 16 --views 4 --surface-points 4096 --seed 43 \
32
+ --max-file-mb 16 --max-download-mb 4096 --reserve-gb 8
33
+ ;;
34
+ train)
35
+ [ -x "$PY" ] || { echo 'Run bash posttrain.sh setup first'; exit 1; }
36
+ "$PY" audit_data.py data/forma3d-v2 upstream/ATTRIBUTIONS.json
37
+ # A fresh output directory is required. Ctrl-C saves last.pt, not optimizer resume.
38
+ caffeinate -i "$PY" -m image3d_student.cli train \
39
+ --manifest data/forma3d-v2/manifest.jsonl --init-from upstream/model.pt \
40
+ --out runs/forma3d-v2 --device mps --width 64 \
41
+ --steps 8000 --batch-size 2 --lr 0.00005 \
42
+ --eval-every 200 --validation-fraction 0.2 --seed 43
43
+ ;;
44
+ evaluate)
45
+ "$PY" stage_v2.py --run runs/forma3d-v2 --data data/forma3d-v2 \
46
+ --base upstream/model.pt --release release/forma3d-tiny-2 --device mps
47
+ ;;
48
+ publish)
49
+ # Explicit command only. Creates a private repo; makes it public after upload.
50
+ "$PY" publish_v2.py release/forma3d-tiny-2
51
+ ;;
52
+ *)
53
+ echo 'Run stages in order: setup, prepare, train, evaluate, publish (optional).'
54
+ echo 'Example: bash posttrain.sh setup'
55
+ ;;
56
+ esac
preparation.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "counts": {
3
+ "scenario-labs/3d-collection": {
4
+ "requested": 128,
5
+ "prepared": 128
6
+ },
7
+ "allenai/objaverse": {
8
+ "requested": 128,
9
+ "prepared": 128
10
+ }
11
+ },
12
+ "samples": 1024,
13
+ "objects": 256,
14
+ "distinct_groups": 239,
15
+ "settings": {
16
+ "scenario": 128,
17
+ "objaverse": 128,
18
+ "objaverse_shards": 16,
19
+ "resume": true,
20
+ "grid": 16,
21
+ "views": 4,
22
+ "surface_points": 4096,
23
+ "seed": 43,
24
+ "max_file_mb": 16,
25
+ "max_download_mb": 4096,
26
+ "reserve_gb": 8.0
27
+ },
28
+ "rendered_proxy_data": true,
29
+ "excluded": [
30
+ "scenario-labs/3d-collection-hunyuan",
31
+ "3dlg-hcvc/MultiScan",
32
+ "huanngzh/3D-Front",
33
+ "facebook/show3d-dataset",
34
+ "Voxel51/gaussian_splatting"
35
+ ]
36
+ }
prepare_v2.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Use pinned upstream preprocessing while excluding every known v1 asset/group."""
2
+ import importlib.util
3
+ import json
4
+ from pathlib import Path
5
+ from audit_data import audit, identity_sets, conflicts
6
+
7
+ ROOT = Path(__file__).resolve().parent
8
+
9
+ def main():
10
+ upstream = ROOT / 'upstream'
11
+ old = json.loads((upstream / 'ATTRIBUTIONS.json').read_text())
12
+ blocked = identity_sets(old)
13
+ spec = importlib.util.spec_from_file_location('pinned_prepare', upstream / 'scripts/prepare_hf.py')
14
+ module = importlib.util.module_from_spec(spec)
15
+ spec.loader.exec_module(module)
16
+ for name in ('scenario_candidates', 'objaverse_candidates'):
17
+ original = getattr(module, name)
18
+ def filtered(*args, _original=original, **kwargs):
19
+ return [r for r in _original(*args, **kwargs) if not conflicts(r, blocked)]
20
+ setattr(module, name, filtered)
21
+ downloader = module.Downloader
22
+ class FilteredDownloader(downloader):
23
+ def get(self, *args, **kwargs):
24
+ path = super().get(*args, **kwargs)
25
+ if module.digest(path) in blocked['sha256']:
26
+ raise ValueError('Excluded byte-identical v1 mesh')
27
+ return path
28
+ module.Downloader = FilteredDownloader
29
+ original_prepare = module.prepare
30
+ def checked_prepare(args):
31
+ out = Path(args.out)
32
+ if (out / 'sources.json').exists():
33
+ for row in json.loads((out / 'sources.json').read_text()):
34
+ if conflicts(row, blocked):
35
+ raise ValueError('Existing dataset overlaps v1; choose a fresh --out directory')
36
+ original_prepare(args)
37
+ audit(out, upstream / 'ATTRIBUTIONS.json')
38
+ module.prepare = checked_prepare
39
+ module.main()
40
+
41
+ if __name__ == '__main__':
42
+ main()
publish_v2.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Explicit optional upload; creates a private repository before making it public."""
2
+ import hashlib
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+ from huggingface_hub import HfApi
7
+
8
+ def main():
9
+ root = Path(sys.argv[1])
10
+ report = json.loads((root/'evaluation.json').read_text())
11
+ if not report.get('passed') or not all(report['checks'].values()):
12
+ raise ValueError('Evaluation gate failed')
13
+ lines = (root/'SHA256SUMS').read_text().splitlines()
14
+ expected = {}
15
+ for line in lines:
16
+ digest, filename = line.split(' ',1)
17
+ path = (root/filename).resolve()
18
+ if not path.is_relative_to(root.resolve()):
19
+ raise ValueError('Invalid checksum path')
20
+ expected[filename] = digest
21
+ if hashlib.sha256(path.read_bytes()).hexdigest() != digest:
22
+ raise ValueError('Changed release file: '+filename)
23
+ actual = {str(p.relative_to(root)) for p in root.rglob('*') if p.is_file() and p.name != 'SHA256SUMS'}
24
+ if actual != set(expected):
25
+ raise ValueError('Unlisted or missing release files')
26
+ repo = 'Lucasgitguy-co/forma3d-tiny-2'
27
+ api = HfApi()
28
+ api.create_repo(repo,repo_type='model',private=True,exist_ok=False)
29
+ api.upload_folder(repo_id=repo,repo_type='model',folder_path=str(root),
30
+ commit_message='Release evaluated Forma3D Tiny 2 continuation')
31
+ api.update_repo_settings(repo_id=repo,repo_type='model',private=False)
32
+ print('Published https://huggingface.co/'+repo)
33
+
34
+ if __name__ == '__main__':
35
+ main()
pyproject.toml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "forma3d-tiny-2"
7
+ version = "0.3.0"
8
+ license = "Apache-2.0"
9
+ license-files = ["LICENSE", "NOTICE", "THIRD_PARTY.md", "licenses/*.txt"]
10
+ readme = "README.md"
11
+ authors = [{name = "Lucasgitguy-co"}]
12
+ description = "Experimental local image-to-Gaussian student with cached teacher supervision"
13
+ requires-python = ">=3.11,<3.14"
14
+ dependencies = ["torch>=2.5", "numpy>=1.26", "pillow>=10", "plyfile>=1", "trimesh>=4", "scipy>=1.11"]
15
+
16
+ [project.optional-dependencies]
17
+ test = ["pytest>=8"]
18
+ data = ["huggingface_hub>=0.34", "pyarrow>=17"]
19
+ teacher = ["idol @ git+https://github.com/speridlabs/idol-sdk.git@4702a7f00850e34b6e83a2170cc123c3e9570744"]
20
+
21
+ [project.scripts]
22
+ image3d = "image3d_student.cli:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
requirements-tested-macos.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio==4.16.0
2
+ click==8.5.0
3
+ filelock==4.2.7
4
+ fsspec==2026.9.0
5
+ h11==0.16.0
6
+ hf-xet==1.8.0
7
+ httpcore2==2.16.0
8
+ httpx2==2.16.0
9
+ huggingface-hub==2.4.0
10
+ idna==3.20
11
+ iniconfig==2.3.1
12
+ jinja2==3.1.6
13
+ markupsafe==3.0.4
14
+ mpmath==1.3.0
15
+ networkx==3.6.1
16
+ numpy==2.4.6
17
+ packaging==26.3
18
+ pillow==13.0.0
19
+ pluggy==1.7.0
20
+ plyfile==1.1.5
21
+ pyarrow==26.0.0
22
+ pygments==2.21.0
23
+ pytest==9.2.0
24
+ pyyaml==6.0.3
25
+ scipy==1.17.1
26
+ setuptools==84.2.1
27
+ sympy==1.14.0
28
+ torch==2.15.0
29
+ tqdm==4.70.1
30
+ trimesh==5.2.0
31
+ truststore==0.10.5
32
+ typing-extensions==4.16.0
scripts/prepare_hf.py ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prepare paired, canonical rendered images and coarse Gaussian targets.
2
+
3
+ These are mesh-derived proxy targets, NOT pretrained-teacher outputs or photos.
4
+ Only public, explicitly allowed assets are downloaded. Source meshes stay local.
5
+ """
6
+ import argparse
7
+ import gzip
8
+ import hashlib
9
+ import json
10
+ import random
11
+ import shutil
12
+ import time
13
+ import urllib.error
14
+ import urllib.request
15
+ from pathlib import Path, PurePosixPath
16
+
17
+ import numpy as np
18
+ import pyarrow.parquet as pq
19
+ import trimesh
20
+ from huggingface_hub import hf_hub_download
21
+
22
+ from image3d_student.data import cache_sample, splat_targets
23
+ from image3d_student.splats import Splats, preview
24
+ from image3d_student.engine import split_rows
25
+
26
+ SCENARIO = ("scenario-labs/3d-collection", "55b4c002b4c1529fc48da3dff98516c3069bfa3f")
27
+ OBJAVERSE = ("allenai/objaverse", "556637099bf4fa79ea7b239d0d9c328b8a2e9ac8")
28
+
29
+
30
+ def digest(path):
31
+ h = hashlib.sha256()
32
+ with Path(path).open("rb") as f:
33
+ for block in iter(lambda: f.read(1024 * 1024), b""):
34
+ h.update(block)
35
+ return h.hexdigest()
36
+
37
+
38
+ class Downloader:
39
+ def __init__(self, root, max_file_mb, max_total_mb, reserve_gb):
40
+ self.root = Path(root)
41
+ self.root.mkdir(parents=True, exist_ok=True)
42
+ self.max_file = int(max_file_mb * 1024**2)
43
+ self.max_total = int(max_total_mb * 1024**2)
44
+ self.reserve = int(reserve_gb * 1024**3)
45
+ self.total = 0
46
+
47
+ def get(self, repo, revision, filename, expected=None):
48
+ parts = PurePosixPath(filename)
49
+ if parts.is_absolute() or ".." in parts.parts or not filename.endswith(".glb"):
50
+ raise ValueError("Only relative GLB paths are allowed")
51
+ out = self.root / repo.replace("/", "--") / revision / filename
52
+ if out.exists():
53
+ if expected and digest(out) != expected:
54
+ raise ValueError("Cached source checksum mismatch")
55
+ if out.stat().st_size > self.max_file:
56
+ raise ValueError("Cached asset exceeds per-file limit")
57
+ return out
58
+ if self.total >= self.max_total:
59
+ raise RuntimeError("Download budget exhausted")
60
+ out.parent.mkdir(parents=True, exist_ok=True)
61
+ url = f"https://huggingface.co/datasets/{repo}/resolve/{revision}/{filename}"
62
+ for attempt in range(4):
63
+ tmp = out.with_suffix(".part")
64
+ try:
65
+ # Public URL only: credentials are never forwarded to asset hosts.
66
+ with urllib.request.urlopen(url, timeout=60) as response, tmp.open("wb") as f:
67
+ length = int(response.headers.get("Content-Length", 0))
68
+ if length > self.max_file:
69
+ raise ValueError("Asset exceeds per-file limit")
70
+ count = 0
71
+ while block := response.read(1024 * 1024):
72
+ count += len(block)
73
+ self.total += len(block)
74
+ if count > self.max_file:
75
+ raise ValueError("Asset exceeds per-file limit")
76
+ if self.total > self.max_total:
77
+ raise RuntimeError("Download budget exhausted")
78
+ if shutil.disk_usage(self.root).free < self.reserve + len(block):
79
+ raise RuntimeError("Free disk space below reserve")
80
+ f.write(block)
81
+ if expected and digest(tmp) != expected:
82
+ raise ValueError("Source checksum mismatch")
83
+ tmp.replace(out)
84
+ return out
85
+ except urllib.error.HTTPError as exc:
86
+ if exc.code not in (429, 500, 502, 503, 504) or attempt == 3:
87
+ raise
88
+ time.sleep(min(2**attempt, 8))
89
+ finally:
90
+ tmp.unlink(missing_ok=True)
91
+ raise RuntimeError("Download retries exhausted")
92
+
93
+
94
+ def scenario_candidates(cache, seed):
95
+ repo, revision = SCENARIO
96
+ path = hf_hub_download(repo, "data/train-00000-of-00001.parquet", repo_type="dataset",
97
+ revision=revision, local_dir=Path(cache) / "metadata/scenario")
98
+ rows = pq.read_table(path, columns=["id", "license", "model", "inputs", "outputs"]).to_pylist()
99
+ # Connected components keep alternate meshes generated from shared inputs together.
100
+ parent = {}
101
+
102
+ def find(x):
103
+ parent.setdefault(x, x)
104
+ if parent[x] != x:
105
+ parent[x] = find(parent[x])
106
+ return parent[x]
107
+
108
+ candidates = []
109
+ for row in rows:
110
+ if row["license"] != "cc-by-4.0" or "hunyuan" in (row["model"] or "").lower():
111
+ continue
112
+ assets = (row.get("inputs") or {}).get("assets") or []
113
+ keys = ["input:" + (a.get("sha256") or a["path"]) for a in assets if a.get("sha256") or a.get("path")]
114
+ keys += ["output:" + o["sha256"] for o in row["outputs"] if o.get("sha256")]
115
+ keys += [row["id"]]
116
+ for key in keys[1:]:
117
+ parent[find(key)] = find(keys[0])
118
+ for output in row["outputs"]:
119
+ if output.get("format") == "glb":
120
+ candidates.append({"repo": repo, "revision": revision, "path": output["path"],
121
+ "sha256": output.get("sha256"), "asset_id": row["id"], "license": "cc-by-4.0",
122
+ "author": "Scenario", "title": row["id"], "source_model": row["model"],
123
+ "source_url": f"https://huggingface.co/datasets/{repo}", "group_key": keys[0]})
124
+ break
125
+ for row in candidates:
126
+ row["object_id"] = "scenario-" + hashlib.sha256(find(row.pop("group_key")).encode()).hexdigest()[:20]
127
+ candidates.sort(key=lambda r: r["asset_id"])
128
+ random.Random(seed).shuffle(candidates)
129
+ return candidates
130
+
131
+
132
+ def objaverse_candidates(cache, seed, shards):
133
+ repo, revision = OBJAVERSE
134
+ candidates = []
135
+ for shard in range(shards):
136
+ part = f"000-{shard:03d}"
137
+ path = hf_hub_download(repo, f"metadata/{part}.json.gz", repo_type="dataset", revision=revision,
138
+ local_dir=Path(cache) / "metadata/objaverse")
139
+ with gzip.open(path) as f:
140
+ records = json.load(f)
141
+ for uid, row in records.items():
142
+ # Start with the more conservative CC0 subset; no NC/SA/unknown assets.
143
+ if row.get("license") != "cc0" or row.get("isAgeRestricted") or row.get("animationCount", 0):
144
+ continue
145
+ candidates.append({"repo": repo, "revision": revision, "path": f"glbs/{part}/{uid}.glb",
146
+ "asset_id": uid, "object_id": "objaverse-" + uid, "license": "cc0-1.0",
147
+ "author": row.get("user", {}).get("displayName", "Unknown"), "title": row.get("name", uid),
148
+ "source_url": row.get("viewerUrl", f"https://sketchfab.com/3d-models/{uid}")})
149
+ candidates.sort(key=lambda r: r["asset_id"])
150
+ random.Random(seed).shuffle(candidates)
151
+ return candidates
152
+
153
+
154
+ def mesh_splats(path, count, grid, seed):
155
+ scene = trimesh.load_scene(path, process=False)
156
+ meshes = []
157
+ for node in scene.graph.nodes_geometry:
158
+ matrix, name = scene.graph[node]
159
+ mesh = scene.geometry[name].copy()
160
+ if not isinstance(mesh, trimesh.Trimesh) or not len(mesh.faces):
161
+ continue
162
+ mesh.apply_transform(matrix)
163
+ meshes.append(mesh)
164
+ if not meshes or sum(len(m.faces) for m in meshes) > 500_000:
165
+ raise ValueError("Missing mesh or more than 500,000 triangles")
166
+ bounds = np.array([m.bounds for m in meshes])
167
+ low, high = bounds[:, 0].min(axis=0), bounds[:, 1].max(axis=0)
168
+ extent = float((high - low).max())
169
+ if not np.isfinite(bounds).all() or extent < 1e-8:
170
+ raise ValueError("Degenerate mesh")
171
+ center = (low + high) / 2
172
+ factor = .9 / extent
173
+ areas = np.array([m.area for m in meshes])
174
+ if not np.isfinite(areas).all() or areas.sum() <= 0:
175
+ raise ValueError("Invalid surface area")
176
+ counts = np.random.default_rng(seed).multinomial(count, areas / areas.sum())
177
+ points, colours = [], []
178
+ for mesh, n in zip(meshes, counts):
179
+ if not n:
180
+ continue
181
+ xyz, faces = trimesh.sample.sample_surface(mesh, int(n), seed=seed)
182
+ bary = trimesh.triangles.points_to_barycentric(mesh.triangles[faces], xyz)
183
+ vis = mesh.visual
184
+ if vis.kind == "texture" and vis.uv is not None:
185
+ uv = (vis.uv[mesh.faces[faces]] * bary[:, :, None]).sum(axis=1)
186
+ sampled = np.asarray(vis.material.to_color(uv))
187
+ if sampled.ndim == 0:
188
+ raise ValueError("Material has no usable texture or base colour")
189
+ # Untextured PBR materials can return one constant RGBA value.
190
+ if sampled.ndim == 1:
191
+ sampled = np.broadcast_to(sampled, (n, len(sampled)))
192
+ colour = sampled[:, :3] / 255.
193
+ elif vis.kind == "vertex":
194
+ colour = (vis.vertex_colors[mesh.faces[faces], :3] * bary[:, :, None]).sum(axis=1) / 255.
195
+ elif vis.kind == "face":
196
+ colour = vis.face_colors[faces, :3] / 255.
197
+ else:
198
+ colour = np.full((n, 3), .65)
199
+ points.append((xyz - center) * factor)
200
+ colours.append(colour)
201
+ xyz = np.concatenate(points).astype(np.float32)
202
+ rgb = np.concatenate(colours).astype(np.float32)
203
+ n = len(xyz)
204
+ splats = Splats(xyz, rgb, np.full((n, 3), .6 / grid),
205
+ np.tile([1, 0, 0, 0], (n, 1)), np.full((n, 1), .9))
206
+ transform = np.eye(4)
207
+ transform[:3, :3] *= factor
208
+ transform[:3, 3] = -center * factor
209
+ return splats, transform.tolist()
210
+
211
+
212
+ def save_progress(out, args, rows, provenance, failures):
213
+ state = {"version": 1, "settings": vars(args), "rows": rows,
214
+ "sources": provenance, "skipped": failures}
215
+ temporary = out / "progress.json.tmp"
216
+ temporary.write_text(json.dumps(state))
217
+ temporary.replace(out / "progress.json")
218
+
219
+
220
+ def check_resume_settings(previous, args):
221
+ for key in ("grid", "views", "surface_points", "seed", "scenario", "objaverse"):
222
+ if previous[key] != getattr(args, key):
223
+ raise ValueError(f"Cannot change {key} while resuming; use a new dataset directory")
224
+
225
+
226
+ def recover_legacy(out, args):
227
+ """Recover the old writer's completed files without rendering or downloading.
228
+
229
+ Old runs saved sources and arrays, but lost manifest rows on insufficient
230
+ candidates. Recreate those rows and verify targets against cached GLBs.
231
+ """
232
+ previous = json.loads((out / "preparation.json").read_text())["settings"]
233
+ check_resume_settings(previous, args)
234
+ provenance = json.loads((out / "sources.json").read_text())
235
+ failures = json.loads((out / "skipped.json").read_text())
236
+ rows = []
237
+ for index, item in enumerate(provenance):
238
+ path = Path(args.cache) / item["repo"].replace("/", "--") / item["revision"] / item["path"]
239
+ if not path.is_file() or digest(path) != item["sha256"]:
240
+ raise ValueError(f"Missing or corrupt cached source for {item['asset_id']}")
241
+ splats, transform = mesh_splats(path, args.surface_points, args.grid,
242
+ int(item["sha256"][:8], 16) ^ args.seed)
243
+ stem = hashlib.sha256((item["repo"] + item["asset_id"]).encode()).hexdigest()[:20]
244
+ for view in range(args.views):
245
+ sample_id = f"{stem}-v{view}"
246
+ image, target = f"images/{sample_id}.png", f"targets/{sample_id}.npz"
247
+ yaw = 2 * np.pi * view / args.views
248
+ c, s = np.cos(yaw), np.sin(yaw)
249
+ rotation = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]], np.float32)
250
+ rotated = Splats(splats.xyz @ rotation.T, splats.rgb, splats.scale, splats.quat, splats.opacity)
251
+ expected = splat_targets(rotated, args.grid)
252
+ with np.load(out / target, allow_pickle=False) as cached:
253
+ if set(cached.files) != set(expected):
254
+ raise ValueError(f"Target fields differ for {sample_id}")
255
+ for key, value in expected.items():
256
+ if not np.array_equal(cached[key], value):
257
+ raise ValueError(f"Recovered target differs for {sample_id}: {key}")
258
+ from PIL import Image
259
+ with Image.open(out / image) as check:
260
+ check.verify()
261
+ rows.append({"id": sample_id, "image": image, "target": target,
262
+ "source": "mesh-proxy:" + item["repo"], "grid": args.grid,
263
+ "teachers": {"appearance": "mesh-proxy:" + item["repo"], "coarse": None, "fine": None},
264
+ "teacher_gaussians": len(splats.xyz), "retained_gaussians": int(expected["occ"].sum()),
265
+ "geometry_transforms": {"coarse": None, "fine": None},
266
+ "image_sha256": digest(out / image), "target_sha256": digest(out / target),
267
+ "metadata": {**item, "normalization_matrix": transform, "yaw_radians": float(yaw),
268
+ "render": "orthographic Gaussian proxy, camera +z",
269
+ "target": "isotropic surface-sampled mesh proxy; not a pretrained teacher"}})
270
+ if (index + 1) % 10 == 0 or index + 1 == len(provenance):
271
+ print(json.dumps({"recovered_objects": index + 1, "existing_objects": len(provenance)}), flush=True)
272
+ save_progress(out, args, rows, provenance, failures)
273
+ return rows, provenance, failures
274
+
275
+
276
+ def load_progress(out, args):
277
+ if not (out / "progress.json").exists():
278
+ return recover_legacy(out, args)
279
+ state = json.loads((out / "progress.json").read_text())
280
+ if state.get("version") != 1:
281
+ raise ValueError("Unsupported preparation progress version")
282
+ check_resume_settings(state["settings"], args)
283
+ for row in state["rows"]:
284
+ for key in ("image", "target"):
285
+ path = (out / row[key]).resolve()
286
+ if not path.is_relative_to(out.resolve()) or digest(path) != row[key + "_sha256"]:
287
+ raise ValueError(f"Prepared file checksum mismatch: {row['id']} {key}")
288
+ return state["rows"], state["sources"], state["skipped"]
289
+
290
+
291
+ def prune_invisible(out, rows, provenance, failures):
292
+ from PIL import Image
293
+ bad = set()
294
+ for row in rows:
295
+ with Image.open(out / row["image"]) as image:
296
+ if image.convert("RGB").getbbox() is None:
297
+ bad.add((row["metadata"]["repo"], row["metadata"]["asset_id"]))
298
+ for repo, asset in sorted(bad):
299
+ failures.append({"repo": repo, "asset": asset,
300
+ "error": "Rendered view has no visible pixels", "terminal": True})
301
+ if bad:
302
+ print(json.dumps({"excluded_invisible_objects": len(bad), "existing_files_preserved": True}), flush=True)
303
+ return ([r for r in rows if (r["metadata"]["repo"], r["metadata"]["asset_id"]) not in bad],
304
+ [p for p in provenance if (p["repo"], p["asset_id"]) not in bad])
305
+
306
+
307
+ def group_duplicates(rows, provenance):
308
+ """Link entire objects when any views or target files are byte-identical."""
309
+ parent = {}
310
+ def find(x):
311
+ parent.setdefault(x, x)
312
+ if parent[x] != x:
313
+ parent[x] = find(parent[x])
314
+ return parent[x]
315
+ hashes = {}
316
+ for row in rows:
317
+ meta = row["metadata"]
318
+ original = meta.setdefault("source_object_id", meta["object_id"])
319
+ find(original)
320
+ for field in ("image_sha256", "target_sha256"):
321
+ key = (field, row[field])
322
+ if key in hashes:
323
+ a, b = find(original), find(hashes[key])
324
+ parent[max(a, b)] = min(a, b)
325
+ else:
326
+ hashes[key] = original
327
+ for row in rows:
328
+ row["metadata"]["object_id"] = find(row["metadata"]["source_object_id"])
329
+ for item in provenance:
330
+ original = item.setdefault("source_object_id", item["object_id"])
331
+ item["object_id"] = find(original)
332
+
333
+
334
+ def prepare(args):
335
+ out = Path(args.out)
336
+ occupied = out.exists() and any(out.iterdir())
337
+ if occupied and not getattr(args, "resume", False):
338
+ raise ValueError("Output is not empty. Use --resume to retain completed objects.")
339
+ if args.scenario + args.objaverse < 4:
340
+ raise ValueError("Request at least four objects")
341
+ out.mkdir(parents=True, exist_ok=True)
342
+ if occupied:
343
+ all_rows, provenance, failures = load_progress(out, args)
344
+ else:
345
+ all_rows, provenance, failures = [], [], []
346
+ save_progress(out, args, all_rows, provenance, failures)
347
+ all_rows, provenance = prune_invisible(out, all_rows, provenance, failures)
348
+ save_progress(out, args, all_rows, provenance, failures)
349
+ seen = {item["sha256"] for item in provenance}
350
+ completed = {(item["repo"], item["asset_id"]) for item in provenance}
351
+ rejected = {(f["repo"], f["asset"]) for f in failures if f.get("terminal")}
352
+ counts = {}
353
+ fetch = Downloader(args.cache, args.max_file_mb, args.max_download_mb, args.reserve_gb)
354
+ sources = []
355
+ if args.scenario:
356
+ ready = sum(item["repo"] == SCENARIO[0] for item in provenance)
357
+ candidates = scenario_candidates(args.cache, args.seed) if ready < args.scenario else []
358
+ sources.append((SCENARIO[0], candidates, args.scenario, ready))
359
+ if args.objaverse:
360
+ ready = sum(item["repo"] == OBJAVERSE[0] for item in provenance)
361
+ candidates = objaverse_candidates(args.cache, args.seed, args.objaverse_shards) if ready < args.objaverse else []
362
+ sources.append((OBJAVERSE[0], candidates, args.objaverse, ready))
363
+ for repo, candidates, wanted, accepted in sources:
364
+ for item in candidates:
365
+ if accepted >= wanted:
366
+ break
367
+ if (item["repo"], item["asset_id"]) in completed | rejected:
368
+ continue
369
+ staging = out / "_staging"
370
+ try:
371
+ path = fetch.get(item["repo"], item["revision"], item["path"], item.get("sha256"))
372
+ checksum = digest(path)
373
+ if checksum in seen:
374
+ continue
375
+ item["sha256"] = checksum
376
+ item_seed = int(checksum[:8], 16) ^ args.seed
377
+ splats, transform = mesh_splats(path, args.surface_points, args.grid, item_seed)
378
+ stem = hashlib.sha256((item["repo"] + item["asset_id"]).encode()).hexdigest()[:20]
379
+ staging.mkdir(parents=True, exist_ok=True)
380
+ rows = []
381
+ for view in range(args.views):
382
+ yaw = 2 * np.pi * view / args.views
383
+ c, s = np.cos(yaw), np.sin(yaw)
384
+ rotation = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]], np.float32)
385
+ rotated = Splats(splats.xyz @ rotation.T, splats.rgb, splats.scale, splats.quat, splats.opacity)
386
+ image = staging / "input.png"
387
+ rendered = preview(rotated, size=128)
388
+ if rendered.getbbox() is None:
389
+ raise ValueError("Rendered view has no visible pixels")
390
+ rendered.save(image)
391
+ metadata = {**item, "object_id": item["object_id"], "normalization_matrix": transform,
392
+ "yaw_radians": float(yaw), "render": "orthographic Gaussian proxy, camera +z",
393
+ "target": "isotropic surface-sampled mesh proxy; not a pretrained teacher"}
394
+ rows.append(cache_sample(staging, f"{stem}-v{view}", image, rotated, args.grid,
395
+ source="mesh-proxy:" + item["repo"], extra=metadata))
396
+ for folder in ("images", "targets"):
397
+ (out / folder).mkdir(exist_ok=True)
398
+ for file in (staging / folder).iterdir():
399
+ file.replace(out / folder / file.name)
400
+ all_rows.extend(rows)
401
+ seen.add(checksum)
402
+ provenance.append(item)
403
+ completed.add((item["repo"], item["asset_id"]))
404
+ accepted += 1
405
+ save_progress(out, args, all_rows, provenance, failures)
406
+ print(json.dumps({"repo": item["repo"], "accepted": accepted, "requested": wanted,
407
+ "asset": item["asset_id"]}), flush=True)
408
+ except RuntimeError:
409
+ raise # resource limits are a hard stop
410
+ except Exception as exc:
411
+ failures.append({"repo": item["repo"], "asset": item["asset_id"],
412
+ "error": type(exc).__name__ + ": " + str(exc)[:200],
413
+ "terminal": "no visible pixels" in str(exc)})
414
+ print(json.dumps({"skipped": item["asset_id"], "reason": failures[-1]["error"]}), flush=True)
415
+ finally:
416
+ shutil.rmtree(staging, ignore_errors=True)
417
+ counts[repo] = {"requested": wanted, "prepared": accepted}
418
+ group_duplicates(all_rows, provenance)
419
+ save_progress(out, args, all_rows, provenance, failures)
420
+ (out / "sources.json").write_text(json.dumps(provenance, indent=2))
421
+ (out / "skipped.json").write_text(json.dumps(failures, indent=2))
422
+ report = {"counts": counts, "samples": len(all_rows), "objects": len(provenance),
423
+ "distinct_groups": len({r["metadata"]["object_id"] for r in all_rows}),
424
+ "settings": vars(args), "rendered_proxy_data": True,
425
+ "excluded": ["scenario-labs/3d-collection-hunyuan", "3dlg-hcvc/MultiScan",
426
+ "huanngzh/3D-Front", "facebook/show3d-dataset", "Voxel51/gaussian_splatting"]}
427
+ (out / "preparation.json").write_text(json.dumps(report, indent=2))
428
+ if any(v["prepared"] < v["requested"] for v in counts.values()):
429
+ raise ValueError("Not enough eligible assets. Completed objects are saved. Rerun with --resume and a larger --objaverse-shards value.")
430
+ if report["distinct_groups"] < 4:
431
+ raise ValueError("Not enough independent object groups")
432
+ # The test partition never enters training or checkpoint selection.
433
+ development, test = split_rows(all_rows, .15, args.seed + 1)
434
+ for name, rows in [("manifest.jsonl", development), ("test-manifest.jsonl", test)]:
435
+ (out / name).write_text("".join(json.dumps(r) + "\n" for r in rows))
436
+ print(json.dumps(report, indent=2))
437
+
438
+
439
+ def main():
440
+ p = argparse.ArgumentParser(description=__doc__)
441
+ p.add_argument("--out", required=True)
442
+ p.add_argument("--cache", default="data/downloads")
443
+ p.add_argument("--scenario", type=int, default=64)
444
+ p.add_argument("--objaverse", type=int, default=32)
445
+ p.add_argument("--objaverse-shards", type=int, default=8)
446
+ p.add_argument("--resume", action="store_true", help="Reuse and verify completed objects, including older failed runs")
447
+ p.add_argument("--grid", type=int, choices=[8, 16, 32], default=16)
448
+ p.add_argument("--views", type=int, default=4)
449
+ p.add_argument("--surface-points", type=int, default=4096)
450
+ p.add_argument("--seed", type=int, default=42)
451
+ p.add_argument("--max-file-mb", type=int, default=16)
452
+ p.add_argument("--max-download-mb", type=int, default=2048)
453
+ p.add_argument("--reserve-gb", type=float, default=2)
454
+ args = p.parse_args()
455
+ if min(args.scenario, args.objaverse) < 0 or not 1 <= args.objaverse_shards <= 160:
456
+ p.error("Invalid object counts or shard count")
457
+ if min(args.views, args.surface_points, args.max_file_mb, args.max_download_mb, args.reserve_gb) <= 0:
458
+ p.error("Counts and limits must be positive")
459
+ prepare(args)
460
+
461
+
462
+ if __name__ == "__main__":
463
+ main()
scripts/release.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluate a completed run, stage a self-contained release, optionally publish it."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ import torch
9
+ from torch.utils.data import DataLoader
10
+
11
+ from image3d_student.data import CachedDataset, load_manifest
12
+ from image3d_student.engine import device_for, evaluate, generate
13
+ from image3d_student.model import Student
14
+
15
+
16
+ def sha(path):
17
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
18
+
19
+
20
+ def evaluate_release(args):
21
+ run, dataset = Path(args.run), Path(args.data)
22
+ meta = json.loads((run / "run.json").read_text())
23
+ checkpoint = torch.load(run / "best.pt", map_location="cpu", weights_only=True)
24
+ last = torch.load(run / "last.pt", map_location="cpu", weights_only=True)
25
+ if last["step"] != meta["steps_requested"]:
26
+ raise ValueError("Training did not complete the requested steps")
27
+ if checkpoint.get("step", 0) < 1 or not meta.get("initialization"):
28
+ raise ValueError("Expected completed post-training from a base checkpoint")
29
+ if sha(args.base) != meta["initialization"]["sha256"]:
30
+ raise ValueError("Base checkpoint differs from the training initialization")
31
+ if sha(dataset / "manifest.jsonl") != meta["manifest_sha256"]:
32
+ raise ValueError("Training manifest changed")
33
+ development = load_manifest(dataset / "manifest.jsonl")
34
+ test = load_manifest(dataset / "test-manifest.jsonl")
35
+ selected_ids = set(meta["train_ids"]) | set(meta["val_ids"])
36
+ if selected_ids != {r["id"] for r in development}:
37
+ raise ValueError("Run split does not match development manifest")
38
+ if {r["metadata"]["object_id"] for r in development} & {r["metadata"]["object_id"] for r in test}:
39
+ raise ValueError("Test object leakage")
40
+ for key in ["image_sha256", "target_sha256"]:
41
+ if {r[key] for r in development} & {r[key] for r in test}:
42
+ raise ValueError("Duplicate data across development and test")
43
+ device = device_for(args.device)
44
+ model = Student(**checkpoint["config"]).to(device)
45
+ model.load_state_dict(checkpoint["model"])
46
+ loader = DataLoader(CachedDataset(dataset / "test-manifest.jsonl"), batch_size=1)
47
+ final = evaluate(model, loader, device)
48
+ base = torch.load(args.base, map_location="cpu", weights_only=True)
49
+ initial = Student(**checkpoint["config"]).to(device)
50
+ result = initial.load_state_dict({k: v for k, v in base["model"].items() if k not in ("coords", "centers")}, strict=False)
51
+ if set(result.missing_keys) != {"coords", "centers"} or result.unexpected_keys:
52
+ raise ValueError("Base architecture mismatch")
53
+ baseline = evaluate(initial, loader, device)
54
+ best_validation = min(json.loads(line)["validation"]["loss"] for line in
55
+ (run / "metrics.jsonl").read_text().splitlines())
56
+ checks = {"validation_loss_improved": best_validation < meta["baseline_validation"]["loss"],
57
+ "test_loss_improved": final["loss"] < baseline["loss"],
58
+ "test_occupancy_iou_improved": final["occupancy_iou"] > baseline["occupancy_iou"],
59
+ "test_occupancy_iou_at_least_005": final["occupancy_iou"] >= .05}
60
+ report = {"checkpoint_sha256": sha(run / "best.pt"), "base_sha256": sha(args.base),
61
+ "test_manifest_sha256": sha(dataset / "test-manifest.jsonl"),
62
+ "test_objects": len({r["metadata"]["object_id"] for r in test}),
63
+ "test_samples": len(test), "baseline_test": baseline, "posttrained_test": final,
64
+ "checks": checks, "passed": all(checks.values()),
65
+ "scope": "Rendered mesh-proxy evaluation only; does not establish real-photo or mesh reconstruction quality"}
66
+ (run / "evaluation.json").write_text(json.dumps(report, indent=2))
67
+ print(json.dumps(report, indent=2), flush=True)
68
+ # Keep qualitative inspection files local; all held-out objects, first view.
69
+ example_dir = run / "inspection"
70
+ example_dir.mkdir(exist_ok=True)
71
+ seen = set()
72
+ from image3d_student.splats import preview, read_ply
73
+ from PIL import Image, ImageDraw
74
+ for row in test:
75
+ group = row["metadata"]["object_id"]
76
+ if group in seen:
77
+ continue
78
+ seen.add(group)
79
+ out = example_dir / (row["id"] + ".ply")
80
+ if not out.exists():
81
+ try:
82
+ generate(run / "best.pt", dataset / row["image"], out, args.device)
83
+ except ValueError as exc:
84
+ if "No occupied cells" not in str(exc):
85
+ raise
86
+ report["checks"]["nonempty_examples"] = False
87
+ report["passed"] = False
88
+ continue
89
+ sheet = Image.new("RGB", (512, 152), "black")
90
+ sheet.paste(Image.open(dataset / row["image"]).convert("RGB").resize((128, 128)), (0, 24))
91
+ splats = read_ply(out)
92
+ for i, angle in enumerate([0, 1.5707963, 3.1415927]):
93
+ sheet.paste(preview(splats, size=128, yaw=angle), (128 * (i + 1), 24))
94
+ ImageDraw.Draw(sheet).text((5, 5), "Input proxy | prediction front | side | back", fill="white")
95
+ sheet.save(out.with_suffix(".png"))
96
+ if len(seen) >= 8:
97
+ break
98
+ (run / "evaluation.json").write_text(json.dumps(report, indent=2))
99
+ if not report["passed"]:
100
+ raise ValueError("Evaluation gate failed. Nothing was published. Inspect evaluation.json and improve data/training.")
101
+ return checkpoint, meta, report
102
+
103
+
104
+ def stage(args, checkpoint, meta, report):
105
+ source = Path(__file__).resolve().parents[1]
106
+ release = Path(args.release)
107
+ if release.exists():
108
+ raise ValueError("Release directory already exists; choose a new --release path")
109
+ release.mkdir(parents=True)
110
+ shutil.copytree(source / "src", release / "src", ignore=shutil.ignore_patterns("__pycache__", "*.egg-info"))
111
+ shutil.copytree(source / "scripts", release / "scripts", ignore=shutil.ignore_patterns("__pycache__"))
112
+ shutil.copytree(source / "licenses", release / "licenses")
113
+ for filename in ["pyproject.toml", "LICENSE", "NOTICE", "THIRD_PARTY.md", "DATASETS.md", "COMMANDS.md", "setup-mac.sh", "train-mac.sh", "requirements-tested-macos.txt"]:
114
+ shutil.copyfile(source / filename, release / filename)
115
+ # Publish inference weights only; optimizer and local absolute paths stay local.
116
+ public_meta = {k: v for k, v in meta.items() if k != "dataset"}
117
+ torch.save({"format": 1, "config": checkpoint["config"], "model": checkpoint["model"],
118
+ "step": checkpoint["step"], "metadata": public_meta}, release / "model.pt")
119
+ (release / "config.json").write_text(json.dumps(checkpoint["config"], indent=2))
120
+ (release / "training.json").write_text(json.dumps(public_meta, indent=2))
121
+ (release / "evaluation.json").write_text(json.dumps({**report,
122
+ "published_model_sha256": sha(release / "model.pt")}, indent=2))
123
+ shutil.copyfile(Path(args.data) / "sources.json", release / "ATTRIBUTIONS.json")
124
+ used = sorted({r["repo"] for r in json.loads((release / "ATTRIBUTIONS.json").read_text())})
125
+ datasets_yaml = "\n".join("- " + repo for repo in used)
126
+ text = f'''---
127
+ license: apache-2.0
128
+ pipeline_tag: image-to-3d
129
+ base_model: Lucasgitguy-co/image3d-student
130
+ model_name: Forma3D-Tiny v0.2
131
+ datasets:
132
+ {datasets_yaml}
133
+ tags:
134
+ - pytorch
135
+ - gaussian-splatting
136
+ - research-prototype
137
+ - mesh-proxy-supervision
138
+ ---
139
+ # Forma3D-Tiny v0.2
140
+
141
+ An experimental compact image-to-Gaussian model post-trained from Image3D Student's
142
+ synthetic demonstration checkpoint. This is a coarse research baseline, not a
143
+ general-purpose photo-to-3D model. Its grid is {checkpoint["config"]["grid"]} cubed and
144
+ it has {meta["parameters"]:,} parameters. Selected checkpoint: step {checkpoint["step"]};
145
+ completed run: {meta["steps_requested"]} steps.
146
+
147
+ ## Actual training data and preprocessing
148
+
149
+ Only the datasets listed above were used, with per-asset provenance in ATTRIBUTIONS.json.
150
+ Scenario assets are CC-BY-4.0; the Objaverse subset is CC0. Original objects were centered
151
+ and uniformly scaled into a 0.9-wide cube, surface sampled, and converted to isotropic
152
+ Gaussian proxy targets. Orthographic images were rendered from these same proxy clouds.
153
+ This is not teacher distillation from Idol, Hunyuan, InstantMesh, or Hi3D, and it did not
154
+ train on natural photographs, MultiScan, 3D-Front, Show3D, or Voxel51 scene splats.
155
+
156
+ Related source inputs and all views stay in the same object group. A separate 15% test
157
+ partition is excluded from training and validation-based checkpoint selection.
158
+
159
+ ## Measured results
160
+
161
+ Independent test objects: {report["test_objects"]}; test views: {report["test_samples"]}.
162
+ Base test loss: {report["baseline_test"]["loss"]:.6f}; post-trained test loss:
163
+ {report["posttrained_test"]["loss"]:.6f}. Base occupancy IoU:
164
+ {report["baseline_test"]["occupancy_iou"]:.6f}; post-trained occupancy IoU:
165
+ {report["posttrained_test"]["occupancy_iou"]:.6f}.
166
+ See evaluation.json for the full results and checks. These scores measure coarse proxy
167
+ reconstruction on the rendered data distribution; they do not prove photo realism,
168
+ watertight meshes, production quality, or texture fidelity. Near duplicates can remain
169
+ despite source grouping and exact-byte deduplication.
170
+
171
+ ## Run on a Mac
172
+
173
+ Download this repository, then run `bash setup-mac.sh` and
174
+ `source "$HOME/Library/Application Support/Forma3D/venv/bin/activate"`.
175
+
176
+ ```sh
177
+ image3d generate --checkpoint model.pt --image your-centered-cutout.png \\
178
+ --out results/object.ply --device mps
179
+ image3d preview --ply results/object.ply --out results/preview.png
180
+ ```
181
+
182
+ Inputs are centered objects on black, matching the training framing. Natural photos
183
+ are outside the verified distribution. Output is Gaussian PLY, not a triangle mesh.
184
+ CPU works with `--device cpu`; CUDA support is implemented but was not tested on the
185
+ Mac used to prepare these scripts.
186
+
187
+ ## License and attribution
188
+
189
+ Source code and these weights are offered under Apache-2.0. Dataset and individual
190
+ asset licenses remain separate; retain ATTRIBUTIONS.json and consult DATASETS.md.
191
+ This license statement does not grant rights to third-party trademarks or likenesses.
192
+ See COMMANDS.md for reproduction commands.
193
+ '''
194
+ (release / "README.md").write_text(text)
195
+ sums = [sha(p) + " " + str(p.relative_to(release)) for p in sorted(release.rglob("*")) if p.is_file()]
196
+ (release / "SHA256SUMS").write_text("\n".join(sums) + "\n")
197
+ return release
198
+
199
+
200
+ def main():
201
+ p = argparse.ArgumentParser(description=__doc__)
202
+ p.add_argument("--run", required=True)
203
+ p.add_argument("--data", required=True)
204
+ p.add_argument("--base", required=True)
205
+ p.add_argument("--release", default="release/forma3d-tiny")
206
+ p.add_argument("--repo", default="Lucasgitguy-co/forma3d-tiny")
207
+ p.add_argument("--device", choices=["mps", "cpu", "cuda", "auto"], default="mps")
208
+ p.add_argument("--publish", action="store_true")
209
+ args = p.parse_args()
210
+ checkpoint, meta, report = evaluate_release(args)
211
+ release = stage(args, checkpoint, meta, report)
212
+ if args.publish:
213
+ from huggingface_hub import HfApi
214
+ api = HfApi()
215
+ # Refuse accidental replacement of a previously published model.
216
+ if api.repo_exists(args.repo, repo_type="model"):
217
+ raise ValueError("Repository already exists. Choose a new --repo; no overwrite was performed.")
218
+ api.create_repo(args.repo, repo_type="model", private=True)
219
+ commit = api.upload_folder(repo_id=args.repo, folder_path=release,
220
+ commit_message="Publish evaluated Forma3D-Tiny v0.2")
221
+ api.update_repo_settings(args.repo, private=False)
222
+ print(f"Published https://huggingface.co/{args.repo} at {commit.oid}")
223
+ else:
224
+ print(f"Evaluated release staged at {release}; nothing published.")
225
+
226
+
227
+ if __name__ == "__main__":
228
+ main()
src/image3d_student/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ """Research prototype; no pretrained general-purpose student weights are bundled."""
4
+
5
+ __version__ = "0.2.0"
src/image3d_student/cli.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ import argparse
4
+ import importlib.util
5
+ import json
6
+ import platform
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import torch
12
+
13
+
14
+ def parser():
15
+ p = argparse.ArgumentParser(description="Local image-to-Gaussian student research prototype")
16
+ sub = p.add_subparsers(dest="command", required=True)
17
+ sub.add_parser("doctor", help="Check dependencies, Metal availability and disk space; no downloads")
18
+ demo = sub.add_parser("demo-data", help="Create synthetic code-test fixtures, NOT teacher data")
19
+ demo.add_argument("--out", required=True)
20
+ demo.add_argument("--count", type=int, default=12)
21
+ demo.add_argument("--grid", type=int, choices=[8,16,32], default=8)
22
+ cache = sub.add_parser("cache", help="Cache Idol PLY with optional aligned geometry teacher meshes")
23
+ cache.add_argument("--image", required=True, help="Exact prepared/framed input used by teacher")
24
+ cache.add_argument("--idol-ply", required=True)
25
+ cache.add_argument("--out", required=True)
26
+ cache.add_argument("--id", required=True)
27
+ cache.add_argument("--object-id", help="Shared ID for all images of the same object, to prevent validation leakage")
28
+ cache.add_argument("--grid", type=int, choices=[8,16,32], default=16)
29
+ cache.add_argument("--coarse-mesh", help="InstantMesh output; this command imports, does not run InstantMesh")
30
+ cache.add_argument("--coarse-transform", help="JSON 4x4 mesh-to-Idol-PLY matrix")
31
+ cache.add_argument("--fine-mesh", help="Hi3D output; this command imports, does not run Hi3D")
32
+ cache.add_argument("--fine-transform", help="JSON 4x4 mesh-to-Idol-PLY matrix")
33
+ train = sub.add_parser("train", help="Train the student on cached targets")
34
+ train.add_argument("--manifest", required=True)
35
+ train.add_argument("--out", required=True)
36
+ train.add_argument("--device", choices=["auto","cuda","mps","cpu"], default="auto")
37
+ train.add_argument("--init-from", help="Post-train checkpoint weights; optimizer starts fresh")
38
+ train.add_argument("--steps", type=int, default=1000)
39
+ train.add_argument("--batch-size", type=int, default=1)
40
+ train.add_argument("--width", type=int, default=128)
41
+ train.add_argument("--lr", type=float, default=3e-4)
42
+ train.add_argument("--seed", type=int, default=42)
43
+ train.add_argument("--eval-every", type=int, default=50)
44
+ train.add_argument("--validation-fraction", type=float, default=.2)
45
+ train.add_argument("--allow-synthetic", action="store_true")
46
+ generate = sub.add_parser("generate", help="Generate standard 3DGS PLY with a trained student")
47
+ generate.add_argument("--checkpoint", required=True)
48
+ generate.add_argument("--image", required=True, help="Centered cutout or teacher-prepared image, black background")
49
+ generate.add_argument("--out", required=True)
50
+ generate.add_argument("--device", choices=["auto","cuda","mps","cpu"], default="auto")
51
+ generate.add_argument("--threshold", type=float, default=.5)
52
+ generate.add_argument("--max-splats", type=int, default=32768)
53
+ render = sub.add_parser("preview", help="CPU diagnostic turntable contact sheet")
54
+ render.add_argument("--ply", required=True)
55
+ render.add_argument("--out", required=True)
56
+ teacher = sub.add_parser("teacher-idol", help="EXPERIMENTAL staged Mac runner; weights not bundled")
57
+ teacher.add_argument("--image", required=True)
58
+ teacher.add_argument("--out", required=True)
59
+ teacher.add_argument("--cache", default=".cache/huggingface")
60
+ teacher.add_argument("--weights", default="speridlabs/idol")
61
+ teacher.add_argument("--device", choices=["mps","cpu"], default="mps")
62
+ teacher.add_argument("--mode", choices=["real","synthetic"], default="synthetic")
63
+ teacher.add_argument("--steps", type=int, default=20)
64
+ teacher.add_argument("--gaussians", type=int, default=32768)
65
+ teacher.add_argument("--query-chunk", type=int, default=128)
66
+ teacher.add_argument("--seed", type=int, default=42)
67
+ teacher.add_argument("--allow-download", action="store_true", help="Explicitly allow downloading model weights and gated encoders")
68
+ return p
69
+
70
+
71
+ def main():
72
+ p = parser()
73
+ args = p.parse_args()
74
+ try:
75
+ if args.command == "doctor":
76
+ report = {"python": platform.python_version(), "platform": platform.platform(), "torch": str(torch.__version__),
77
+ "mps_available": torch.backends.mps.is_available(), "mps_built": torch.backends.mps.is_built(),
78
+ "free_disk_gib": round(shutil.disk_usage(Path.cwd()).free / 1024**3, 2),
79
+ "idol_installed": importlib.util.find_spec("idol") is not None,
80
+ "teacher_status": {"Idol": "experimental adapter, real weights unverified",
81
+ "InstantMesh": "cached mesh import only; no Mac execution adapter",
82
+ "Hi3D": "cached mesh import only; no Mac execution adapter"},
83
+ "weights_bundled": False, "quantization": "FP32 student; no binary kernels or 1-bit claims"}
84
+ print(json.dumps(report, indent=2))
85
+ elif args.command == "demo-data":
86
+ from .demo import create_demo
87
+ print(create_demo(args.out, args.count, args.grid))
88
+ elif args.command == "cache":
89
+ from .data import cache_sample
90
+ from .splats import read_ply
91
+ print(json.dumps(cache_sample(args.out, args.id, args.image, read_ply(args.idol_ply), args.grid,
92
+ coarse=args.coarse_mesh, coarse_transform=args.coarse_transform,
93
+ fine=args.fine_mesh, fine_transform=args.fine_transform,
94
+ extra={"object_id": args.object_id or args.id, "idol_ply": str(Path(args.idol_ply).resolve())}), indent=2))
95
+ elif args.command == "train":
96
+ from .engine import train
97
+ train(args)
98
+ elif args.command == "generate":
99
+ from .engine import generate
100
+ print(json.dumps(generate(args.checkpoint, args.image, args.out, args.device, args.threshold, args.max_splats), indent=2))
101
+ elif args.command == "preview":
102
+ from PIL import Image, ImageDraw
103
+ import numpy as np
104
+ from .splats import preview, read_ply
105
+ splats = read_ply(args.ply)
106
+ sheet = Image.new("RGB", (1024, 280), "#111111")
107
+ draw = ImageDraw.Draw(sheet)
108
+ for i in range(4):
109
+ sheet.paste(preview(splats, yaw=i*np.pi/2), (i*256, 24))
110
+ draw.text((i*256+8, 6), f"{i*90} degrees - diagnostic view", fill="white")
111
+ out = Path(args.out)
112
+ if out.exists():
113
+ raise ValueError("Preview already exists")
114
+ out.parent.mkdir(parents=True, exist_ok=True)
115
+ sheet.save(out)
116
+ elif args.command == "teacher-idol":
117
+ from .teacher_idol import run_idol
118
+ run_idol(args)
119
+ except (ValueError, RuntimeError, FileNotFoundError, ImportError) as exc:
120
+ print(f"Error: {exc}", file=sys.stderr)
121
+ return 2
122
+ return 0
123
+
124
+
125
+ if __name__ == "__main__":
126
+ raise SystemExit(main())
src/image3d_student/data.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ import torch
9
+ from PIL import Image, ImageOps
10
+ from torch.utils.data import Dataset
11
+
12
+ from .splats import Splats, read_ply
13
+
14
+
15
+ def grid_centers(grid):
16
+ axis = (np.arange(grid, dtype=np.float32) + .5) / grid - .5
17
+ return np.stack(np.meshgrid(axis, axis, axis, indexing="ij"), axis=-1).reshape(-1, 3)
18
+
19
+
20
+ def cell_indices(xyz, grid):
21
+ if not np.isfinite(xyz).all() or (np.abs(xyz) > .50001).any():
22
+ raise ValueError("Targets must be aligned inside [-0.5,0.5]^3. No silent normalization is performed.")
23
+ ijk = np.floor((xyz + .5) * grid).astype(np.int64).clip(0, grid-1)
24
+ return (ijk[:, 0] * grid + ijk[:, 1]) * grid + ijk[:, 2]
25
+
26
+
27
+ def splat_targets(splats, grid):
28
+ """One highest-opacity teacher Gaussian per occupied cell; explicitly lossy."""
29
+ cells = cell_indices(splats.xyz, grid)
30
+ order = np.argsort(-splats.opacity[:, 0], kind="stable")
31
+ _, first = np.unique(cells[order], return_index=True)
32
+ keep = order[first]
33
+ slots = cells[keep]
34
+ result = {"occ": np.zeros((grid**3, 1), np.float32), "xyz": grid_centers(grid),
35
+ "rgb": np.zeros((grid**3, 3), np.float32),
36
+ "scale": np.full((grid**3, 3), 1 / grid / 3, np.float32),
37
+ "quat": np.tile(np.array([1, 0, 0, 0], np.float32), (grid**3, 1)),
38
+ "opacity": np.zeros((grid**3, 1), np.float32),
39
+ "geometry": np.zeros((grid**3, 2), np.float32),
40
+ "geometry_valid": np.zeros(2, np.float32), "grid": np.array(grid, np.int32)}
41
+ result["occ"][slots] = 1
42
+ for key in ("xyz", "rgb", "scale", "quat", "opacity"):
43
+ result[key][slots] = getattr(splats, key)[keep]
44
+ return result
45
+
46
+
47
+ def mesh_occupancy(path, transform_path, grid):
48
+ import trimesh
49
+ if not transform_path:
50
+ raise ValueError("Every geometry teacher needs an explicit --*-transform JSON matrix, even for identity.")
51
+ transform = np.asarray(json.loads(Path(transform_path).read_text()), dtype=float)
52
+ if transform.shape != (4, 4) or not np.isfinite(transform).all() or not np.allclose(transform[3], [0,0,0,1]):
53
+ raise ValueError("Transform must be a finite affine 4x4 matrix")
54
+ mesh = trimesh.load(str(path), force="mesh", process=False)
55
+ if not isinstance(mesh, trimesh.Trimesh) or len(mesh.faces) == 0:
56
+ raise ValueError("Geometry teacher must contain a triangle mesh")
57
+ mesh.apply_transform(transform)
58
+ cell_indices(np.asarray(mesh.vertices), grid) # validates the whole object
59
+ points, _ = trimesh.sample.sample_surface(mesh, min(200_000, grid**3 * 16), seed=42)
60
+ mask = np.zeros(grid**3, np.float32)
61
+ mask[np.unique(cell_indices(points, grid))] = 1
62
+ return mask
63
+
64
+
65
+ def cache_sample(root, sample_id, image_path, splats, grid=16, source="idol",
66
+ coarse=None, coarse_transform=None, fine=None, fine_transform=None, extra=None):
67
+ root = Path(root)
68
+ if not sample_id or any(c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" for c in sample_id):
69
+ raise ValueError("Sample ID may contain only letters, digits, - and _")
70
+ if grid not in (8, 16, 32):
71
+ raise ValueError("Grid must be 8, 16 or 32")
72
+ target = splat_targets(splats, grid)
73
+ for channel, mesh, transform in [(0, coarse, coarse_transform), (1, fine, fine_transform)]:
74
+ if mesh:
75
+ target["geometry"][:, channel] = mesh_occupancy(mesh, transform, grid)
76
+ target["geometry_valid"][channel] = 1
77
+ row = {"id": sample_id, "image": f"images/{sample_id}.png", "target": f"targets/{sample_id}.npz",
78
+ "source": source, "grid": grid, "teachers": {"appearance": source,
79
+ "coarse": "InstantMesh" if coarse else None, "fine": "Hi3D" if fine else None},
80
+ "teacher_gaussians": len(splats.xyz), "retained_gaussians": int(target["occ"].sum()),
81
+ "geometry_transforms": {"coarse": json.loads(Path(coarse_transform).read_text()) if coarse else None,
82
+ "fine": json.loads(Path(fine_transform).read_text()) if fine else None},
83
+ "metadata": extra or {}}
84
+ manifest = root / "manifest.jsonl"
85
+ rows = load_manifest(manifest) if manifest.exists() else []
86
+ if any(r["id"] == sample_id for r in rows):
87
+ raise ValueError(f"Sample {sample_id} already exists; choose a new ID")
88
+ if rows and any(r["grid"] != grid for r in rows):
89
+ raise ValueError("Do not mix grid resolutions in one dataset")
90
+ image = Image.open(image_path)
91
+ image.load()
92
+ if root.joinpath(row["image"]).exists() or root.joinpath(row["target"]).exists():
93
+ raise ValueError("Sample files already exist; refusing to overwrite")
94
+ root.joinpath("images").mkdir(parents=True, exist_ok=True)
95
+ root.joinpath("targets").mkdir(parents=True, exist_ok=True)
96
+ image.save(root / row["image"])
97
+ row["image_sha256"] = hashlib.sha256((root / row["image"]).read_bytes()).hexdigest()
98
+ np.savez_compressed(root / row["target"], **target)
99
+ row["target_sha256"] = hashlib.sha256((root / row["target"]).read_bytes()).hexdigest()
100
+ # Single-writer dataset. Atomic manifest replacement avoids partial JSON lines.
101
+ tmp = manifest.with_suffix(".tmp")
102
+ tmp.write_text("".join(json.dumps(r) + "\n" for r in [*rows, row]))
103
+ tmp.replace(manifest)
104
+ return row
105
+
106
+
107
+ def load_manifest(path):
108
+ rows = [json.loads(line) for line in Path(path).read_text().splitlines() if line.strip()]
109
+ if not rows or len({r["id"] for r in rows}) != len(rows):
110
+ raise ValueError("Manifest must contain nonempty, unique sample IDs")
111
+ return rows
112
+
113
+
114
+ def image_tensor(path, size=128):
115
+ """Already-framed RGB or RGBA cutout; black background, square letterbox."""
116
+ with Image.open(path) as original:
117
+ rgba = ImageOps.exif_transpose(original).convert("RGBA")
118
+ bg = Image.new("RGBA", rgba.size, (0, 0, 0, 255))
119
+ bg.alpha_composite(rgba)
120
+ image = ImageOps.pad(bg.convert("RGB"), (size, size), method=Image.Resampling.LANCZOS, color="black")
121
+ return torch.from_numpy(np.array(image, dtype=np.float32).transpose(2, 0, 1) / 255)
122
+
123
+
124
+ class CachedDataset(Dataset):
125
+ def __init__(self, manifest, rows=None):
126
+ self.root = Path(manifest).resolve().parent
127
+ self.rows = load_manifest(manifest) if rows is None else rows
128
+ if not self.rows:
129
+ raise ValueError("Empty dataset")
130
+ self.grid = self.rows[0]["grid"]
131
+ for row in self.rows:
132
+ if row["grid"] != self.grid:
133
+ raise ValueError("Mixed grids")
134
+ for key in ("image", "target"):
135
+ p = (self.root / row[key]).resolve()
136
+ if not p.is_relative_to(self.root) or not p.is_file():
137
+ raise ValueError(f"Invalid dataset {key} path")
138
+ digest = hashlib.sha256(p.read_bytes()).hexdigest()
139
+ if digest != row[f"{key}_sha256"]:
140
+ raise ValueError(f"Dataset {key} hash mismatch for {row['id']}")
141
+
142
+ def __len__(self):
143
+ return len(self.rows)
144
+
145
+ def __getitem__(self, i):
146
+ row = self.rows[i]
147
+ with np.load(self.root / row["target"], allow_pickle=False) as cached:
148
+ target = {k: torch.from_numpy(cached[k].copy()).float() for k in cached.files if k != "grid"}
149
+ return image_tensor(self.root / row["image"]), target
src/image3d_student/demo.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+
7
+ from .data import cache_sample
8
+ from .splats import Splats, preview
9
+
10
+
11
+ def create_demo(root, count=12, grid=8):
12
+ """Procedural ellipsoids only. Never presented as outputs of any real teacher."""
13
+ if count < 4:
14
+ raise ValueError("Use at least four fixtures")
15
+ root = Path(root)
16
+ root.mkdir(parents=True, exist_ok=True)
17
+ rng = np.random.default_rng(17)
18
+ phi, theta = np.meshgrid(np.linspace(.06, np.pi-.06, 14), np.linspace(0, 2*np.pi, 28, endpoint=False))
19
+ unit = np.stack([np.sin(phi)*np.cos(theta), np.cos(phi), np.sin(phi)*np.sin(theta)], axis=-1).reshape(-1, 3)
20
+ for i in range(count):
21
+ radii = rng.uniform(.17, .36, 3)
22
+ xyz = (unit * radii).astype(np.float32)
23
+ colour = rng.uniform(.2, .95, 3)
24
+ rgb = np.clip(colour[None] * (.8 + .2 * unit[:, 1:2]), 0, 1)
25
+ splats = Splats(xyz, rgb, np.full_like(xyz, .035), np.tile([1,0,0,0], (len(xyz),1)), np.full((len(xyz),1), .9))
26
+ image_path = root / f"fixture-{i:03}.png"
27
+ preview(splats, size=128).save(image_path)
28
+ cache_sample(root, f"fixture-{i:03}", image_path, splats, grid, source="synthetic",
29
+ extra={"purpose": "code verification only", "object_id": f"fixture-{i:03}"})
30
+ image_path.unlink()
31
+ return root / "manifest.jsonl"
src/image3d_student/engine.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ import json
4
+ import hashlib
5
+ import random
6
+ import time
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import torch
11
+ from torch.utils.data import DataLoader
12
+
13
+ from .data import CachedDataset, image_tensor, load_manifest
14
+ from .model import Student, distillation_loss
15
+ from .splats import Splats, write_ply
16
+
17
+
18
+ def device_for(name="auto"):
19
+ if name == "auto":
20
+ name = "cuda" if torch.cuda.is_available() else ("mps" if torch.backends.mps.is_available() else "cpu")
21
+ if name == "mps" and not torch.backends.mps.is_available():
22
+ raise ValueError("Metal GPU unavailable in this process. Run in a normal macOS Terminal, or use --device cpu.")
23
+ if name == "cuda" and not torch.cuda.is_available():
24
+ raise ValueError("CUDA requested but unavailable")
25
+ if name not in ("mps", "cpu", "cuda"):
26
+ raise ValueError("Supported devices: cpu, mps, cuda")
27
+ torch.set_num_threads(min(4, torch.get_num_threads()))
28
+ return torch.device(name)
29
+
30
+
31
+ def split_rows(rows, fraction, seed):
32
+ if not 0 <= fraction < 1:
33
+ raise ValueError("Validation fraction must lie in [0,1)")
34
+ # Keep repeated views/crops of the same object together. Supply object_id in cache metadata.
35
+ groups = {}
36
+ for row in rows:
37
+ group = row.get("metadata", {}).get("object_id", row["id"])
38
+ groups.setdefault(group, []).append(row)
39
+ keys = sorted(groups)
40
+ random.Random(seed).shuffle(keys)
41
+ n = max(1, round(len(keys) * fraction)) if fraction else 0
42
+ if n >= len(keys):
43
+ raise ValueError("Need at least two different objects for validation, or explicitly use --validation-fraction 0 for an overfit test")
44
+ val_keys = set(keys[:n])
45
+ train = [r for k in keys if k not in val_keys for r in groups[k]]
46
+ val = [r for k in keys if k in val_keys for r in groups[k]]
47
+ if {r["image_sha256"] for r in train} & {r["image_sha256"] for r in val}:
48
+ raise ValueError("Identical images occur across training/validation. Assign the same object-id to related samples.")
49
+ return train, val
50
+
51
+
52
+ def on_device(target, device):
53
+ return {k: v.to(device) for k, v in target.items()}
54
+
55
+
56
+ @torch.no_grad()
57
+ def evaluate(model, loader, device):
58
+ model.eval()
59
+ sums = {}
60
+ for image, target in loader:
61
+ target = on_device(target, device)
62
+ pred = model(image.to(device))
63
+ loss, terms = distillation_loss(pred, target, model.grid)
64
+ occupied = pred["occ"].sigmoid() > .5
65
+ truth = target["occ"] > .5
66
+ terms = {**terms, "loss": loss, "occupancy_iou": (occupied & truth).sum() / (occupied | truth).sum().clamp_min(1)}
67
+ for key, value in terms.items():
68
+ sums[key] = sums.get(key, 0.) + float(value)
69
+ return {k: v / len(loader) for k, v in sums.items()}
70
+
71
+
72
+ def save_checkpoint(path, model, optimizer, step, metadata):
73
+ payload = {"format": 1, "config": model.config, "model": {k: v.detach().cpu() for k, v in model.state_dict().items()},
74
+ "optimizer": optimizer.state_dict(), "step": step, "metadata": metadata}
75
+ tmp = Path(path).with_suffix(".tmp")
76
+ torch.save(payload, tmp)
77
+ tmp.replace(path)
78
+
79
+
80
+ def train(args):
81
+ if args.steps < 1 or args.batch_size < 1 or args.lr <= 0 or args.eval_every < 1:
82
+ raise ValueError("Steps, batch size, learning rate, and evaluation interval must be positive")
83
+ device = device_for(args.device)
84
+ random.seed(args.seed)
85
+ np.random.seed(args.seed)
86
+ torch.manual_seed(args.seed)
87
+ rows = load_manifest(args.manifest)
88
+ synthetic = any(r["source"] == "synthetic" for r in rows)
89
+ if synthetic and not args.allow_synthetic:
90
+ raise ValueError("Synthetic fixtures are code tests, not teacher data. Add --allow-synthetic for an explicit smoke test.")
91
+ train_rows, val_rows = split_rows(rows, args.validation_fraction, args.seed)
92
+ train_data = CachedDataset(args.manifest, train_rows)
93
+ generator = torch.Generator().manual_seed(args.seed)
94
+ loader = DataLoader(train_data, batch_size=args.batch_size, shuffle=True, num_workers=0, generator=generator)
95
+ val_loader = DataLoader(CachedDataset(args.manifest, val_rows), batch_size=1) if val_rows else None
96
+ model = Student(train_data.grid, args.width).to(device)
97
+ initialization = None
98
+ if getattr(args, "init_from", None):
99
+ initial_path = Path(args.init_from)
100
+ state = torch.load(initial_path, map_location="cpu", weights_only=True)
101
+ if state.get("format") != 1 or state["config"]["width"] != args.width:
102
+ raise ValueError("Initialization requires format=1 and matching --width")
103
+ # Coordinates are deterministic buffers, not learned weights. Rebuild them
104
+ # for the target grid while preserving every learned encoder/decoder weight.
105
+ learned = {k: v for k, v in state["model"].items() if k not in ("centers", "coords")}
106
+ result = model.load_state_dict(learned, strict=False)
107
+ if set(result.missing_keys) != {"centers", "coords"} or result.unexpected_keys:
108
+ raise ValueError("Checkpoint architecture mismatch")
109
+ initialization = {"sha256": hashlib.sha256(initial_path.read_bytes()).hexdigest(),
110
+ "config": state["config"], "step": state["step"],
111
+ "optimizer": "fresh AdamW; weights-only post-training"}
112
+ optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
113
+ run = Path(args.out)
114
+ if run.exists() and any(run.iterdir()):
115
+ raise ValueError("Run directory must be empty; choose a new --out directory")
116
+ run.mkdir(parents=True, exist_ok=True)
117
+ meta = {"synthetic_only": all(r["source"] == "synthetic" for r in rows),
118
+ "contains_synthetic": synthetic, "device": str(device), "torch": str(torch.__version__),
119
+ "seed": args.seed, "train_ids": [r["id"] for r in train_rows], "val_ids": [r["id"] for r in val_rows],
120
+ "parameters": sum(p.numel() for p in model.parameters()),
121
+ "teachers": sorted({v for r in rows for v in r["teachers"].values() if v}),
122
+ "validation": "held-out objects" if val_rows else "none; training overfit test only",
123
+ "dataset": str(Path(args.manifest).resolve()),
124
+ "manifest_sha256": hashlib.sha256(Path(args.manifest).read_bytes()).hexdigest(),
125
+ "initialization": initialization, "steps_requested": args.steps,
126
+ "learning_rate": args.lr, "batch_size": args.batch_size,
127
+ "validation_fraction": args.validation_fraction}
128
+ if val_loader:
129
+ meta["baseline_validation"] = evaluate(model, val_loader, device)
130
+ (run / "run.json").write_text(json.dumps(meta, indent=2))
131
+ print(json.dumps(meta), flush=True)
132
+ step, best, started = 0, float("inf"), time.monotonic()
133
+ iterator = iter(loader)
134
+ try:
135
+ while step < args.steps:
136
+ try:
137
+ images, target = next(iterator)
138
+ except StopIteration:
139
+ iterator = iter(loader)
140
+ images, target = next(iterator)
141
+ model.train()
142
+ optimizer.zero_grad(set_to_none=True)
143
+ pred = model(images.to(device))
144
+ loss, terms = distillation_loss(pred, on_device(target, device), model.grid)
145
+ if not torch.isfinite(loss):
146
+ raise RuntimeError("Nonfinite training loss; stopping")
147
+ loss.backward()
148
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.)
149
+ optimizer.step()
150
+ step += 1
151
+ if step == 1 or step % args.eval_every == 0 or step == args.steps:
152
+ record = {"step": step, "seconds": round(time.monotonic()-started, 3),
153
+ "train_loss": float(loss.detach()), **{k: float(v.detach()) for k, v in terms.items()}}
154
+ if val_loader:
155
+ record["validation"] = evaluate(model, val_loader, device)
156
+ score = record["validation"]["loss"]
157
+ if score < best:
158
+ best = score
159
+ save_checkpoint(run / "best.pt", model, optimizer, step, meta)
160
+ with (run / "metrics.jsonl").open("a") as handle:
161
+ handle.write(json.dumps(record) + "\n")
162
+ print(json.dumps(record), flush=True)
163
+ save_checkpoint(run / "last.pt", model, optimizer, step, meta)
164
+ except KeyboardInterrupt:
165
+ print("Interrupted; saving completed optimizer steps.", flush=True)
166
+ finally:
167
+ if step:
168
+ save_checkpoint(run / "last.pt", model, optimizer, step, meta)
169
+ return meta
170
+
171
+
172
+ @torch.no_grad()
173
+ def generate(checkpoint, image, out, device="auto", threshold=.5, max_splats=32768):
174
+ if not 0 < threshold < 1 or max_splats < 1:
175
+ raise ValueError("Threshold must be in (0,1) and max-splats positive")
176
+ device = device_for(device)
177
+ state = torch.load(checkpoint, map_location="cpu", weights_only=True)
178
+ if state.get("format") != 1 or state.get("step", 0) < 1:
179
+ raise ValueError("Expected a trained image3d-student checkpoint")
180
+ model = Student(**state["config"])
181
+ model.load_state_dict(state["model"])
182
+ model.to(device).eval()
183
+ if device.type == "mps":
184
+ torch.mps.synchronize()
185
+ started = time.monotonic()
186
+ pred = model(image_tensor(image)[None].to(device))
187
+ score = pred["occ"][0, :, 0].sigmoid()
188
+ valid = torch.nonzero(score >= threshold).flatten()
189
+ if len(valid) == 0:
190
+ raise ValueError("No occupied cells above threshold. Train longer or inspect validation; no fabricated fallback splats were exported.")
191
+ valid = valid[torch.argsort(score[valid], descending=True)[:max_splats]]
192
+ values = {k: pred[k][0, valid].cpu().numpy() for k in ("xyz", "rgb", "scale", "quat", "opacity")}
193
+ elapsed = time.monotonic() - started
194
+ splats = Splats(**values)
195
+ out = Path(out)
196
+ if out.exists() or out.with_suffix(".json").exists():
197
+ raise ValueError("Output already exists; choose a new filename")
198
+ write_ply(out, splats)
199
+ report = {"gaussians": len(valid), "seconds_preprocess_and_forward": elapsed,
200
+ "device": str(device), "checkpoint_step": state["step"], "training": state["metadata"],
201
+ "note": ("Synthetic-trained output is a pipeline demonstration, not a pretrained image-to-3D result."
202
+ if state["metadata"].get("contains_synthetic") else "Experimental cached-target student; inspect unseen-object quality.")}
203
+ out.with_suffix(".json").write_text(json.dumps(report, indent=2))
204
+ return report
src/image3d_student/model.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ import math
4
+
5
+ import torch
6
+ from torch import nn
7
+ from torch.nn import functional as F
8
+
9
+ from .data import grid_centers
10
+
11
+
12
+ class Student(nn.Module):
13
+ """Small image-conditioned coordinate MLP; fixed cells avoid unordered point regression.
14
+
15
+ 15 Gaussian outputs + 2 auxiliary geometry logits. This is a coarse baseline,
16
+ not an architecture expected to reproduce million-splat teacher detail.
17
+ """
18
+ def __init__(self, grid=16, width=128):
19
+ super().__init__()
20
+ if grid not in (8, 16, 32) or width < 16:
21
+ raise ValueError("Unsupported grid or width")
22
+ self.config = {"grid": grid, "width": width}
23
+ self.grid = grid
24
+ blocks, channels = [], 3
25
+ for out in (24, 48, 96, 128):
26
+ blocks.extend([nn.Conv2d(channels, out, 3, stride=2, padding=1), nn.GroupNorm(8, out), nn.SiLU()])
27
+ channels = out
28
+ self.encoder = nn.Sequential(*blocks, nn.AdaptiveAvgPool2d(4), nn.Flatten(),
29
+ nn.Linear(128*4*4, width), nn.SiLU())
30
+ centers = torch.from_numpy(grid_centers(grid))
31
+ freq = 2**torch.arange(4, dtype=torch.float32) * math.pi * 2
32
+ angles = centers[..., None] * freq
33
+ self.register_buffer("centers", centers)
34
+ self.register_buffer("coords", torch.cat([centers, angles.sin().flatten(1), angles.cos().flatten(1)], dim=1))
35
+ self.query = nn.Sequential(nn.Linear(27, width), nn.SiLU())
36
+ self.decoder = nn.Sequential(nn.Linear(width, width), nn.SiLU(), nn.Linear(width, width), nn.SiLU(), nn.Linear(width, 17))
37
+ with torch.no_grad():
38
+ self.decoder[-1].bias.zero_()
39
+ self.decoder[-1].bias[0] = -2
40
+
41
+ def forward(self, images):
42
+ features = self.encoder(images)
43
+ raw = self.decoder(features[:, None, :] + self.query(self.coords)[None])
44
+ quat = raw[..., 10:14] + raw.new_tensor([1, 0, 0, 0])
45
+ return {"occ": raw[..., :1], "xyz": self.centers[None] + raw[..., 1:4].tanh() / (2*self.grid),
46
+ "rgb": raw[..., 4:7].sigmoid(),
47
+ "scale": (raw[..., 7:10].clamp(-8, 5) - math.log(self.grid*3)).exp(),
48
+ "quat": F.normalize(quat, dim=-1, eps=1e-8), "opacity": raw[..., 14:15].sigmoid(),
49
+ "geometry": raw[..., 15:17]}
50
+
51
+
52
+ def balanced_bce(logits, target):
53
+ error = F.binary_cross_entropy_with_logits(logits, target, reduction="none")
54
+ positive = (error * target).sum() / target.sum().clamp_min(1)
55
+ negative = (error * (1-target)).sum() / (1-target).sum().clamp_min(1)
56
+ return (positive + negative) / 2
57
+
58
+
59
+ def distillation_loss(pred, target, grid):
60
+ mask = target["occ"]
61
+ def masked(value):
62
+ return (value * mask).sum() / (mask.sum().clamp_min(1) * value.shape[-1])
63
+ terms = {"occupancy": balanced_bce(pred["occ"], mask),
64
+ "position": masked(((pred["xyz"] - target["xyz"]) * grid).square()),
65
+ "colour": masked((pred["rgb"] - target["rgb"].clamp(0, 1)).square()),
66
+ "scale": masked((pred["scale"].log() - target["scale"].clamp_min(1e-8).log()).square()),
67
+ "rotation": masked(1 - (pred["quat"] * target["quat"]).sum(-1, keepdim=True).square().clamp(0, 1)),
68
+ "opacity": masked((pred["opacity"] - target["opacity"]).square())}
69
+ geometry = pred["geometry"].sum() * 0
70
+ for i in range(2):
71
+ available = target["geometry_valid"][:, i] > 0
72
+ if available.any():
73
+ geometry = geometry + balanced_bce(pred["geometry"][available, :, i:i+1], target["geometry"][available, :, i:i+1])
74
+ terms["geometry"] = geometry
75
+ total = terms["occupancy"] + terms["position"] + 2*terms["colour"] + .1*terms["scale"] + .1*terms["rotation"] + terms["opacity"] + .25*geometry
76
+ return total, terms
src/image3d_student/splats.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ """Activated Gaussian attributes and standard degree-zero 3DGS PLY IO.
4
+
5
+ Coordinates are preserved as supplied. Idol's *exported PLY* uses its y-up frame;
6
+ we do not apply Idol's internal z-up conversion a second time.
7
+ """
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ from plyfile import PlyData, PlyElement
13
+
14
+ C0 = 0.28209479177387814
15
+
16
+
17
+ @dataclass
18
+ class Splats:
19
+ xyz: np.ndarray
20
+ rgb: np.ndarray
21
+ scale: np.ndarray
22
+ quat: np.ndarray # w,x,y,z
23
+ opacity: np.ndarray
24
+
25
+ def __post_init__(self):
26
+ n = len(self.xyz)
27
+ if n == 0:
28
+ raise ValueError("No Gaussians: refusing to write or train on an empty object.")
29
+ for name, width in [("xyz", 3), ("rgb", 3), ("scale", 3), ("quat", 4), ("opacity", 1)]:
30
+ a = np.asarray(getattr(self, name), dtype=np.float32).reshape(-1, width)
31
+ if a.shape != (n, width) or not np.isfinite(a).all():
32
+ raise ValueError(f"Invalid {name} shape or nonfinite values")
33
+ setattr(self, name, a)
34
+ if (self.scale <= 0).any():
35
+ raise ValueError("Gaussian scales must be positive")
36
+ if (self.opacity < 0).any() or (self.opacity > 1).any():
37
+ raise ValueError("Opacity must lie in [0,1]")
38
+ norm = np.linalg.norm(self.quat, axis=1, keepdims=True)
39
+ if (norm < 1e-8).any():
40
+ raise ValueError("Zero quaternion")
41
+ self.quat = self.quat / norm
42
+
43
+ def take(self, indices):
44
+ return Splats(**{k: getattr(self, k)[indices] for k in self.__dataclass_fields__})
45
+
46
+
47
+ def read_ply(path):
48
+ v = PlyData.read(str(path))["vertex"].data
49
+ required = ["x", "y", "z", "f_dc_0", "f_dc_1", "f_dc_2", "opacity",
50
+ "scale_0", "scale_1", "scale_2", "rot_0", "rot_1", "rot_2", "rot_3"]
51
+ if not set(required).issubset(v.dtype.names):
52
+ raise ValueError("Expected a Gaussian-splat PLY, not a mesh or ordinary point-cloud PLY")
53
+ col = lambda names: np.column_stack([v[k] for k in names]).astype(np.float32)
54
+ return Splats(col(["x", "y", "z"]), 0.5 + C0 * col([f"f_dc_{i}" for i in range(3)]),
55
+ np.exp(np.clip(col([f"scale_{i}" for i in range(3)]), -20, 5)),
56
+ col([f"rot_{i}" for i in range(4)]),
57
+ 1 / (1 + np.exp(-np.clip(col(["opacity"]), -30, 30))))
58
+
59
+
60
+ def write_ply(path, splats):
61
+ path = Path(path)
62
+ path.parent.mkdir(parents=True, exist_ok=True)
63
+ names = ["x", "y", "z", "nx", "ny", "nz", "f_dc_0", "f_dc_1", "f_dc_2", "opacity",
64
+ "scale_0", "scale_1", "scale_2", "rot_0", "rot_1", "rot_2", "rot_3"]
65
+ opacity = np.clip(splats.opacity, 1e-6, 1 - 1e-6)
66
+ values = np.concatenate([splats.xyz, np.zeros_like(splats.xyz), (splats.rgb - 0.5) / C0,
67
+ np.log(opacity / (1 - opacity)), np.log(splats.scale), splats.quat], axis=1)
68
+ table = np.empty(len(values), dtype=[(k, "f4") for k in names])
69
+ for i, name in enumerate(names):
70
+ table[name] = values[:, i]
71
+ PlyData([PlyElement.describe(table, "vertex")], text=False).write(str(path))
72
+
73
+
74
+ def rotation_matrices(q):
75
+ w, x, y, z = q.T
76
+ return np.stack([1-2*(y*y+z*z), 2*(x*y-z*w), 2*(x*z+y*w),
77
+ 2*(x*y+z*w), 1-2*(x*x+z*z), 2*(y*z-x*w),
78
+ 2*(x*z-y*w), 2*(y*z+x*w), 1-2*(x*x+y*y)], axis=-1).reshape(-1, 3, 3)
79
+
80
+
81
+ def preview(splats, size=256, yaw=0.0):
82
+ """CPU orthographic Gaussian preview; debugging only, not an evaluation renderer."""
83
+ from PIL import Image
84
+ c, s = np.cos(yaw), np.sin(yaw)
85
+ view = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]], dtype=np.float32)
86
+ xyz = splats.xyz @ view.T
87
+ rot = rotation_matrices(splats.quat)
88
+ cov = (rot * (splats.scale ** 2)[:, None, :]) @ rot.transpose(0, 2, 1)
89
+ cov = view[None] @ cov @ view.T[None]
90
+ pixels = size / 1.25
91
+ canvas = np.zeros((size, size, 3), dtype=np.float32)
92
+ # Back to front, camera on +z. Image y points down.
93
+ for i in np.argsort(xyz[:, 2]):
94
+ center = np.array([xyz[i, 0], -xyz[i, 1]]) * pixels + size / 2
95
+ flip = np.diag([1., -1.])
96
+ covariance = flip @ cov[i, :2, :2] @ flip * pixels ** 2 + np.eye(2) * 0.3
97
+ radius = min(size, int(np.ceil(3 * np.sqrt(np.linalg.eigvalsh(covariance).max()))))
98
+ lo = np.maximum(0, np.floor(center - radius).astype(int))
99
+ hi = np.minimum(size, np.ceil(center + radius + 1).astype(int))
100
+ if np.any(hi <= lo):
101
+ continue
102
+ yy, xx = np.mgrid[lo[1]:hi[1], lo[0]:hi[0]]
103
+ delta = np.stack([xx - center[0], yy - center[1]], axis=-1)
104
+ exponent = np.einsum("...i,ij,...j->...", delta, np.linalg.inv(covariance), delta)
105
+ alpha = (splats.opacity[i, 0] * np.exp(-0.5 * exponent)).clip(0, .99)[..., None]
106
+ patch = canvas[lo[1]:hi[1], lo[0]:hi[0]]
107
+ patch[:] = patch * (1-alpha) + np.clip(splats.rgb[i], 0, 1) * alpha
108
+ return Image.fromarray((canvas.clip(0, 1) * 255).astype(np.uint8))
src/image3d_student/teacher_idol.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Lucasgitguy-co
3
+ """Experimental, staged Idol inference for Apple MPS.
4
+
5
+ Upstream revision: 4702a7f00850e34b6e83a2170cc123c3e9570744.
6
+ No weights are bundled. Full-weight inference has NOT been verified by this project.
7
+ """
8
+ import gc
9
+ import json
10
+ import os
11
+ import shutil
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import torch
16
+ from PIL import Image
17
+
18
+ from .engine import device_for
19
+
20
+
21
+ def patch_attention(layers, query_chunk=128):
22
+ """Bound attention working memory by splitting only the query dimension.
23
+
24
+ Each query still sees ALL keys/values. Does not split or change softmax's key axis.
25
+ Disable CUDA-specific optimized sampling and avoid complex RoPE tensors on MPS.
26
+ """
27
+ if query_chunk < 1:
28
+ raise ValueError("query_chunk must be positive")
29
+ from torch.nn import functional as F
30
+
31
+ def attention(*args):
32
+ q, k, v = args[0].unbind(dim=2) if len(args) == 1 else args
33
+ q, k, v = (t.permute(0, 2, 1, 3) for t in (q, k, v))
34
+ chunks = [F.scaled_dot_product_attention(q[:, :, start:start+query_chunk], k, v)
35
+ for start in range(0, q.shape[2], query_chunk)]
36
+ return torch.cat(chunks, dim=2).permute(0, 2, 1, 3)
37
+
38
+ layers.scaled_dot_product_attention = attention
39
+ layers.RePo3DRotaryEmbedding.real_valued = True
40
+ return attention
41
+
42
+
43
+ def release(device):
44
+ gc.collect()
45
+ if device.type == "mps":
46
+ torch.mps.synchronize()
47
+ torch.mps.empty_cache()
48
+
49
+
50
+ @torch.no_grad()
51
+ def run_idol(args):
52
+ if args.steps < 1 or args.gaussians < 32768 or args.gaussians > 1048576 or args.gaussians % 32:
53
+ raise ValueError("Steps must be positive; Idol requires 32768..1048576 Gaussians, divisible by 32")
54
+ device = device_for(args.device)
55
+ cache = Path(args.cache).resolve()
56
+ cache.mkdir(parents=True, exist_ok=True)
57
+ os.environ["HF_HOME"] = str(cache)
58
+ os.environ["HF_HUB_OFFLINE"] = "0" if args.allow_download else "1"
59
+ os.environ["ATTN_BACKEND"] = "sdpa"
60
+ if args.allow_download and shutil.disk_usage(cache).free < 16 * 1024**3:
61
+ raise ValueError("Less than 16 GiB disk space available. This conservative download guard leaves room for teacher weights, encoders and temporary files. Free space, or use an existing complete cache without --allow-download.")
62
+ try:
63
+ from huggingface_hub import hf_hub_download, snapshot_download
64
+ from safetensors.torch import load_file
65
+ from tensordict import TensorDict
66
+ from idol.conditioning import ImageEncoder, require_encoder_access
67
+ from idol.models import BiRefNet, GaussianFixedlenDecoder, LatentSeqMMFlowModel, OctreeProbabilityFixedlenDecoder, layers
68
+ from idol.preprocess import CROP_MARGIN, preprocess_image, has_real_alpha
69
+ from idol.sampler import sample_flow
70
+ from idol.camera import decode_camera
71
+ except ImportError as exc:
72
+ raise RuntimeError("Install the optional teacher dependencies: python -m pip install '.[teacher]'") from exc
73
+ out = Path(args.out)
74
+ if out.exists():
75
+ raise ValueError("Teacher output directory already exists; choose a new --out")
76
+ # Local-only by default. Hugging Face authentication is never written to project files.
77
+ config_path = hf_hub_download(args.weights, "config.json", local_files_only=not args.allow_download)
78
+ config = json.loads(Path(config_path).read_text())
79
+ encoders = config["image_encoders"]
80
+ require_encoder_access([encoders["dinov3"], encoders["flux2_vae"]])
81
+ weights = Path(snapshot_download(args.weights, allow_patterns=["config.json", "*.safetensors"],
82
+ local_files_only=not args.allow_download))
83
+ patch_attention(layers, args.query_chunk)
84
+ torch.manual_seed(args.seed)
85
+
86
+ def load(model, name):
87
+ state = load_file(str(weights / f"{name}.safetensors"), device="cpu")
88
+ model.load_state_dict(state, strict=True)
89
+ del state
90
+ return model.eval().requires_grad_(False).to(device)
91
+
92
+ print("Experimental Idol MPS adapter: full-weight compatibility and quality remain unverified.", flush=True)
93
+ with Image.open(args.image) as opened:
94
+ image = opened.copy()
95
+ background = None
96
+ if not has_real_alpha(image):
97
+ print("Stage 1: background removal", flush=True)
98
+ background = load(BiRefNet(), "birefnet")
99
+ prepared = preprocess_image(image, background, config["image_size"], CROP_MARGIN[args.mode])
100
+ del background
101
+ release(device)
102
+ pixels = torch.from_numpy(np.array(prepared)).permute(2, 0, 1).float()[None].div(255).to(device)
103
+ print("Stage 2: image conditioning", flush=True)
104
+ encoder = ImageEncoder(encoders["dinov3"], encoders["flux2_vae"], device="cpu")
105
+ # Both encoders initially reside on CPU; only the active encoder is moved to MPS.
106
+ encoder.dinov3.to(device)
107
+ feature1 = encoder._dinov3_tokens(pixels)
108
+ del encoder.dinov3
109
+ release(device)
110
+ encoder.flux2_vae.to(device)
111
+ feature2 = encoder._flux2_tokens(pixels)
112
+ del encoder, pixels
113
+ release(device)
114
+ cond = TensorDict({"feature1": feature1, "feature2": feature2}, batch_size=1, device=device)
115
+ print("Stage 3: latent sampling", flush=True)
116
+ model = load(LatentSeqMMFlowModel(**config["denoiser"]["args"]), "denoiser")
117
+ noise = TensorDict({"latent": torch.randn(1, model.q_token_length, model.in_channels, device=device),
118
+ "camera": torch.randn(1, 1, model.cam_channels, device=device)}, batch_size=1, device=device)
119
+ result = sample_flow(model, noise, cond=cond, neg_cond=cond.apply(torch.zeros_like), steps=args.steps,
120
+ rescale_t=3., cfg_strength=3., verbose=True, batch_cfg=False)
121
+ latent, camera = result["latent"], decode_camera(result["camera"][0, 0])
122
+ del model, noise, cond, feature1, feature2, result
123
+ release(device)
124
+ print("Stage 4: octree decoding", flush=True)
125
+ model = load(OctreeProbabilityFixedlenDecoder(**config["decoder"]["args"]), "decoder")
126
+ per_anchor = config["decoder_gs"]["args"]["representation_config"]["num_gaussians"]
127
+ anchors = OctreeProbabilityFixedlenDecoder.sample(model, latent, num_points=args.gaussians // per_anchor,
128
+ level=config["max_voxel_level"])
129
+ del model
130
+ release(device)
131
+ print("Stage 5: Gaussian decoding", flush=True)
132
+ model = load(GaussianFixedlenDecoder(**config["decoder_gs"]["args"]), "decoder_gs")
133
+ gaussian = model.to_representation(anchors, model(anchors, latent))[0]
134
+ out.mkdir(parents=True)
135
+ gaussian.save_ply(str(out / "teacher.ply"))
136
+ prepared.save(out / "input.png")
137
+ (out / "metadata.json").write_text(json.dumps({"teacher": args.weights, "steps": args.steps,
138
+ "gaussians": args.gaussians, "seed": args.seed, "device": str(device), "camera": camera,
139
+ "sdk_revision": "4702a7f00850e34b6e83a2170cc123c3e9570744", "weights_snapshot": weights.name,
140
+ "coordinate_frame": "Idol exported PLY (y-up)", "adapter": "experimental staged MPS"}, indent=2))
141
+ print(f"Teacher output saved to {out}", flush=True)
stage_v2.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Evaluate versus pinned forma3d-tiny, stage honest v2 metadata, never upload."""
2
+ import argparse
3
+ import hashlib
4
+ import importlib.util
5
+ import json
6
+ import shutil
7
+ from pathlib import Path
8
+ import torch
9
+ from audit_data import audit
10
+
11
+ ROOT = Path(__file__).resolve().parent
12
+ REV = 'e80b8d895cc2e405894473abe4ce40b3472eaca7'
13
+ BASE = 'Lucasgitguy-co/forma3d-tiny'
14
+
15
+ def sha(p):
16
+ return hashlib.sha256(Path(p).read_bytes()).hexdigest()
17
+
18
+
19
+ def main():
20
+ p = argparse.ArgumentParser()
21
+ for name in ('run','data','base','release'):
22
+ p.add_argument('--'+name, required=True)
23
+ p.add_argument('--device', default='mps', choices=['mps','cpu'])
24
+ args = p.parse_args()
25
+ destination = Path(args.release)
26
+ if destination.exists():
27
+ raise ValueError('Release directory exists; choose a fresh --release path')
28
+ audit(args.data, ROOT / 'upstream/ATTRIBUTIONS.json')
29
+ # Ensure the asserted parent is the pinned public checkpoint, not another run.
30
+ if sha(args.base) != '83c065d2a50cf086928542cf8d40dc4416b379e38b9aa61d842dc9106d4b3306':
31
+ raise ValueError('Expected the pinned published forma3d-tiny model.pt')
32
+ spec = importlib.util.spec_from_file_location('pinned_release', ROOT / 'upstream/scripts/release.py')
33
+ module = importlib.util.module_from_spec(spec)
34
+ spec.loader.exec_module(module)
35
+ checkpoint, meta, report = module.evaluate_release(args)
36
+ # Upstream gate checks completed training, validation loss, held-out loss/IoU,
37
+ # source separation, initialization hash and nonempty qualitative predictions.
38
+ source = ROOT / 'upstream'
39
+ destination.mkdir(parents=True)
40
+ for folder in ('src','licenses'):
41
+ shutil.copytree(source / folder, destination / folder,
42
+ ignore=shutil.ignore_patterns('__pycache__','*.egg-info'))
43
+ for name in ('LICENSE','NOTICE','THIRD_PARTY.md','DATASETS.md','requirements-tested-macos.txt'):
44
+ shutil.copyfile(source / name, destination / name)
45
+ project = (source / 'pyproject.toml').read_text()
46
+ project = project.replace('name = "forma3d-tiny"','name = "forma3d-tiny-2"')
47
+ project = project.replace('version = "0.2.0"','version = "0.3.0"')
48
+ (destination / 'pyproject.toml').write_text(project)
49
+ (destination / 'scripts').mkdir()
50
+ for name in ('prepare_hf.py','release.py'):
51
+ shutil.copyfile(source / 'scripts' / name, destination / 'scripts' / name)
52
+ # This file set can reproduce the training recipe from pinned upstream.
53
+ for name in ('posttrain.sh','prepare_v2.py','audit_data.py','stage_v2.py','publish_v2.py','COMMANDS.md'):
54
+ shutil.copyfile(ROOT / name, destination / name)
55
+ public_meta = {k:v for k,v in meta.items() if k != 'dataset'}
56
+ public_meta.update({'base_model':BASE, 'base_revision':REV,
57
+ 'training_method':'weights-only continuation; fresh AdamW',
58
+ 'known_v1_assets_excluded':True})
59
+ torch.save({'format':1,'config':checkpoint['config'],'model':checkpoint['model'],
60
+ 'step':checkpoint['step'],'metadata':public_meta}, destination / 'model.pt')
61
+ (destination / 'config.json').write_text(json.dumps(checkpoint['config'],indent=2))
62
+ (destination / 'training.json').write_text(json.dumps(public_meta,indent=2))
63
+ report.update({'base_model':BASE,'base_revision':REV,
64
+ 'published_model_sha256':sha(destination / 'model.pt')})
65
+ (destination / 'evaluation.json').write_text(json.dumps(report,indent=2))
66
+ for src, dst in [('sources.json','ATTRIBUTIONS.json'),('v2-audit.json','data-audit.json'),
67
+ ('preparation.json','preparation.json')]:
68
+ # preparation.json includes user-local paths; publish only portable fields.
69
+ if src == 'preparation.json':
70
+ prepared = json.loads((Path(args.data) / src).read_text())
71
+ prepared['settings'] = {k:v for k,v in prepared['settings'].items() if k not in ('out','cache')}
72
+ (destination / dst).write_text(json.dumps(prepared,indent=2))
73
+ else:
74
+ shutil.copyfile(Path(args.data) / src,destination / dst)
75
+ shutil.copyfile(Path(args.run) / 'metrics.jsonl',destination / 'metrics.jsonl')
76
+ if (ROOT / 'runs/environment.txt').exists():
77
+ lines = (ROOT / 'runs/environment.txt').read_text().splitlines()
78
+ portable = [line for line in lines if ' @ file:' not in line and not line.startswith('-e ')]
79
+ portable.append('# Local forma3d-tiny 0.2.0 source: '+BASE+' @ '+REV)
80
+ (destination / 'environment.txt').write_text('\n'.join(portable)+'\n')
81
+ datasets = sorted({r['repo'] for r in json.loads((destination / 'ATTRIBUTIONS.json').read_text())})
82
+ dataset_yaml = '\n'.join('- '+d for d in datasets)
83
+ bt, ft = report['baseline_test'],report['posttrained_test']
84
+ card = f'''---
85
+ license: apache-2.0
86
+ pipeline_tag: image-to-3d
87
+ base_model: {BASE}
88
+ base_model_relation: finetune
89
+ model_name: Forma3D Tiny 2
90
+ datasets:
91
+ {dataset_yaml}
92
+ tags:
93
+ - pytorch
94
+ - gaussian-splatting
95
+ - mesh-proxy-supervision
96
+ - research-prototype
97
+ ---
98
+ # Forma3D Tiny 2
99
+
100
+ A {meta['parameters']:,}-parameter image-to-Gaussian research model continued from
101
+ {BASE} at revision `{REV}`. The optimizer starts fresh. It uses a
102
+ {checkpoint['config']['grid']} cubed grid and width {checkpoint['config']['width']}.
103
+ Selected checkpoint step {checkpoint['step']} from {meta['steps_requested']} completed steps.
104
+
105
+ ## Training
106
+
107
+ See training.json and metrics.jsonl for actual optimizer settings and validation
108
+ history. Inputs are orthographic renders of centered mesh-derived Gaussian proxy
109
+ clouds on black. Targets are isotropic surface samples compressed to one Gaussian
110
+ per occupied grid cell. No pretrained 3D teacher was run. No natural-photo training
111
+ or differentiable rendering is claimed. Asset IDs, known source groups, and exact
112
+ mesh hashes from all 96 known v1 assets were excluded. Near duplicates can remain.
113
+ The separate 15 percent object-group test split does not enter checkpoint selection;
114
+ 20 percent of development groups supply validation. Counts are realized counts in
115
+ preparation.json and data-audit.json, not assumed independent asset counts.
116
+
117
+ ## Evaluation
118
+
119
+ Both checkpoints were evaluated on the same new held-out proxy data.
120
+
121
+ | Metric | Starting Forma3D Tiny | Selected Forma3D Tiny 2 |
122
+ |---|---:|---:|
123
+ | Mean test loss | {bt['loss']:.6f} | {ft['loss']:.6f} |
124
+ | Mean view occupancy IoU at threshold 0.5 | {bt['occupancy_iou']:.6f} | {ft['occupancy_iou']:.6f} |
125
+
126
+ Test groups: {report['test_objects']}; views: {report['test_samples']}.
127
+ See evaluation.json for hashes and release gates. Metrics are view-averaged over
128
+ proxy renders. They do not establish photo realism, novel-view image quality,
129
+ mesh quality, texture fidelity, gameplay, or general-purpose 3D reconstruction.
130
+
131
+ ## Use
132
+
133
+ Install this source with Python 3.11 and `uv pip install ".[data]"` in a virtual
134
+ environment, then run:
135
+
136
+ ```bash
137
+ image3d generate --checkpoint model.pt --image your-centered-cutout.png \\
138
+ --out results/object.ply --device mps
139
+ image3d preview --ply results/object.ply --out results/preview.png
140
+ ```
141
+
142
+ Output is Gaussian PLY. Inputs should match centered objects on black. See
143
+ COMMANDS.md for the complete continuation recipe. This model retains the same
144
+ architecture; no text-prompt conditioning or new mesh decoder was added.
145
+
146
+ ## License
147
+
148
+ Publisher source and weights are offered under Apache 2.0. Retain NOTICE,
149
+ THIRD_PARTY.md and ATTRIBUTIONS.json. CC BY Scenario assets and CC0 Objaverse
150
+ assets have their own attribution and license records in DATASETS.md.
151
+ '''
152
+ (destination / 'README.md').write_text(card)
153
+ sums = [sha(f)+' '+str(f.relative_to(destination)) for f in sorted(destination.rglob('*')) if f.is_file()]
154
+ (destination / 'SHA256SUMS').write_text('\n'.join(sums)+'\n')
155
+ print('Staged evaluated release at',destination)
156
+ print('Inspect PNG files under',Path(args.run)/'inspection','before optional publication.')
157
+
158
+ if __name__ == '__main__':
159
+ main()
training.json ADDED
@@ -0,0 +1,914 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "synthetic_only": false,
3
+ "contains_synthetic": false,
4
+ "device": "mps",
5
+ "torch": "2.15.0",
6
+ "seed": 43,
7
+ "train_ids": [
8
+ "35d930c154dd6808d06a-v0",
9
+ "35d930c154dd6808d06a-v1",
10
+ "35d930c154dd6808d06a-v2",
11
+ "35d930c154dd6808d06a-v3",
12
+ "58da43454d31f401a82a-v0",
13
+ "58da43454d31f401a82a-v1",
14
+ "58da43454d31f401a82a-v2",
15
+ "58da43454d31f401a82a-v3",
16
+ "d4453f35dd55b9fdbe77-v0",
17
+ "d4453f35dd55b9fdbe77-v1",
18
+ "d4453f35dd55b9fdbe77-v2",
19
+ "d4453f35dd55b9fdbe77-v3",
20
+ "53abbbe27d8cae248360-v0",
21
+ "53abbbe27d8cae248360-v1",
22
+ "53abbbe27d8cae248360-v2",
23
+ "53abbbe27d8cae248360-v3",
24
+ "083ef10612b14e4fd91f-v0",
25
+ "083ef10612b14e4fd91f-v1",
26
+ "083ef10612b14e4fd91f-v2",
27
+ "083ef10612b14e4fd91f-v3",
28
+ "dcd7b3ffd85049cfe762-v0",
29
+ "dcd7b3ffd85049cfe762-v1",
30
+ "dcd7b3ffd85049cfe762-v2",
31
+ "dcd7b3ffd85049cfe762-v3",
32
+ "89f2af1771b298088496-v0",
33
+ "89f2af1771b298088496-v1",
34
+ "89f2af1771b298088496-v2",
35
+ "89f2af1771b298088496-v3",
36
+ "aa87bf05321d963c3de9-v0",
37
+ "aa87bf05321d963c3de9-v1",
38
+ "aa87bf05321d963c3de9-v2",
39
+ "aa87bf05321d963c3de9-v3",
40
+ "8b5e78df6cc3bc96ca2a-v0",
41
+ "8b5e78df6cc3bc96ca2a-v1",
42
+ "8b5e78df6cc3bc96ca2a-v2",
43
+ "8b5e78df6cc3bc96ca2a-v3",
44
+ "d186a120c94371ef7a9d-v0",
45
+ "d186a120c94371ef7a9d-v1",
46
+ "d186a120c94371ef7a9d-v2",
47
+ "d186a120c94371ef7a9d-v3",
48
+ "13fc4338908a8073ab71-v0",
49
+ "13fc4338908a8073ab71-v1",
50
+ "13fc4338908a8073ab71-v2",
51
+ "13fc4338908a8073ab71-v3",
52
+ "95b87385ad6d55cb43a9-v0",
53
+ "95b87385ad6d55cb43a9-v1",
54
+ "95b87385ad6d55cb43a9-v2",
55
+ "95b87385ad6d55cb43a9-v3",
56
+ "a9324f8dc45d1616a80a-v0",
57
+ "a9324f8dc45d1616a80a-v1",
58
+ "a9324f8dc45d1616a80a-v2",
59
+ "a9324f8dc45d1616a80a-v3",
60
+ "3b24fd8b39813b859983-v0",
61
+ "3b24fd8b39813b859983-v1",
62
+ "3b24fd8b39813b859983-v2",
63
+ "3b24fd8b39813b859983-v3",
64
+ "4f6ac99f7fd499a7d43c-v0",
65
+ "4f6ac99f7fd499a7d43c-v1",
66
+ "4f6ac99f7fd499a7d43c-v2",
67
+ "4f6ac99f7fd499a7d43c-v3",
68
+ "42d2395a94668a58c4ad-v0",
69
+ "42d2395a94668a58c4ad-v1",
70
+ "42d2395a94668a58c4ad-v2",
71
+ "42d2395a94668a58c4ad-v3",
72
+ "b1deb9d963a2cd12e16c-v0",
73
+ "b1deb9d963a2cd12e16c-v1",
74
+ "b1deb9d963a2cd12e16c-v2",
75
+ "b1deb9d963a2cd12e16c-v3",
76
+ "b4afe853eaef7b154d1f-v0",
77
+ "b4afe853eaef7b154d1f-v1",
78
+ "b4afe853eaef7b154d1f-v2",
79
+ "b4afe853eaef7b154d1f-v3",
80
+ "12e6fa6b9b3fde9b0daf-v0",
81
+ "12e6fa6b9b3fde9b0daf-v1",
82
+ "12e6fa6b9b3fde9b0daf-v2",
83
+ "12e6fa6b9b3fde9b0daf-v3",
84
+ "8b2af62279572f1c348d-v0",
85
+ "8b2af62279572f1c348d-v1",
86
+ "8b2af62279572f1c348d-v2",
87
+ "8b2af62279572f1c348d-v3",
88
+ "9b2928d8e9b6e432b208-v0",
89
+ "9b2928d8e9b6e432b208-v1",
90
+ "9b2928d8e9b6e432b208-v2",
91
+ "9b2928d8e9b6e432b208-v3",
92
+ "0b603cd8889a5a16f625-v0",
93
+ "0b603cd8889a5a16f625-v1",
94
+ "0b603cd8889a5a16f625-v2",
95
+ "0b603cd8889a5a16f625-v3",
96
+ "aa7a5a2f97543fcc8c24-v0",
97
+ "aa7a5a2f97543fcc8c24-v1",
98
+ "aa7a5a2f97543fcc8c24-v2",
99
+ "aa7a5a2f97543fcc8c24-v3",
100
+ "d4547a44652027eec362-v0",
101
+ "d4547a44652027eec362-v1",
102
+ "d4547a44652027eec362-v2",
103
+ "d4547a44652027eec362-v3",
104
+ "09eda16a9ffee9043ad8-v0",
105
+ "09eda16a9ffee9043ad8-v1",
106
+ "09eda16a9ffee9043ad8-v2",
107
+ "09eda16a9ffee9043ad8-v3",
108
+ "149bc550a730196bfb3e-v0",
109
+ "149bc550a730196bfb3e-v1",
110
+ "149bc550a730196bfb3e-v2",
111
+ "149bc550a730196bfb3e-v3",
112
+ "ba456d938777d3d97c7a-v0",
113
+ "ba456d938777d3d97c7a-v1",
114
+ "ba456d938777d3d97c7a-v2",
115
+ "ba456d938777d3d97c7a-v3",
116
+ "b1d9ca1078489bcc8b59-v0",
117
+ "b1d9ca1078489bcc8b59-v1",
118
+ "b1d9ca1078489bcc8b59-v2",
119
+ "b1d9ca1078489bcc8b59-v3",
120
+ "e2774da2d6fdbf320005-v0",
121
+ "e2774da2d6fdbf320005-v1",
122
+ "e2774da2d6fdbf320005-v2",
123
+ "e2774da2d6fdbf320005-v3",
124
+ "9accb35147475a98151b-v0",
125
+ "9accb35147475a98151b-v1",
126
+ "9accb35147475a98151b-v2",
127
+ "9accb35147475a98151b-v3",
128
+ "9154f51711aa5cad741c-v0",
129
+ "9154f51711aa5cad741c-v1",
130
+ "9154f51711aa5cad741c-v2",
131
+ "9154f51711aa5cad741c-v3",
132
+ "714a6ef2a9b86176887e-v0",
133
+ "714a6ef2a9b86176887e-v1",
134
+ "714a6ef2a9b86176887e-v2",
135
+ "714a6ef2a9b86176887e-v3",
136
+ "f475d0b020f840a08da6-v0",
137
+ "f475d0b020f840a08da6-v1",
138
+ "f475d0b020f840a08da6-v2",
139
+ "f475d0b020f840a08da6-v3",
140
+ "b4becf168c3c3e72d1a1-v0",
141
+ "b4becf168c3c3e72d1a1-v1",
142
+ "b4becf168c3c3e72d1a1-v2",
143
+ "b4becf168c3c3e72d1a1-v3",
144
+ "a93c298b61fac88b7815-v0",
145
+ "a93c298b61fac88b7815-v1",
146
+ "a93c298b61fac88b7815-v2",
147
+ "a93c298b61fac88b7815-v3",
148
+ "f098047bad1f2c42e522-v0",
149
+ "f098047bad1f2c42e522-v1",
150
+ "f098047bad1f2c42e522-v2",
151
+ "f098047bad1f2c42e522-v3",
152
+ "6f5650ce949ed44855db-v0",
153
+ "6f5650ce949ed44855db-v1",
154
+ "6f5650ce949ed44855db-v2",
155
+ "6f5650ce949ed44855db-v3",
156
+ "d835f0e933e6a5401e5f-v0",
157
+ "d835f0e933e6a5401e5f-v1",
158
+ "d835f0e933e6a5401e5f-v2",
159
+ "d835f0e933e6a5401e5f-v3",
160
+ "e141279821ff0cd00133-v0",
161
+ "e141279821ff0cd00133-v1",
162
+ "e141279821ff0cd00133-v2",
163
+ "e141279821ff0cd00133-v3",
164
+ "8a833c632913fb94bcfc-v0",
165
+ "8a833c632913fb94bcfc-v1",
166
+ "8a833c632913fb94bcfc-v2",
167
+ "8a833c632913fb94bcfc-v3",
168
+ "d588a44a75ac91946615-v0",
169
+ "d588a44a75ac91946615-v1",
170
+ "d588a44a75ac91946615-v2",
171
+ "d588a44a75ac91946615-v3",
172
+ "161a50c2c6a490dba3b4-v0",
173
+ "161a50c2c6a490dba3b4-v1",
174
+ "161a50c2c6a490dba3b4-v2",
175
+ "161a50c2c6a490dba3b4-v3",
176
+ "f7ad3a2b9911e7f6ea52-v0",
177
+ "f7ad3a2b9911e7f6ea52-v1",
178
+ "f7ad3a2b9911e7f6ea52-v2",
179
+ "f7ad3a2b9911e7f6ea52-v3",
180
+ "e44219edeb2584e028c7-v0",
181
+ "e44219edeb2584e028c7-v1",
182
+ "e44219edeb2584e028c7-v2",
183
+ "e44219edeb2584e028c7-v3",
184
+ "a47cb57e5c8f24e11587-v0",
185
+ "a47cb57e5c8f24e11587-v1",
186
+ "a47cb57e5c8f24e11587-v2",
187
+ "a47cb57e5c8f24e11587-v3",
188
+ "0975f7262f09261c93cf-v0",
189
+ "0975f7262f09261c93cf-v1",
190
+ "0975f7262f09261c93cf-v2",
191
+ "0975f7262f09261c93cf-v3",
192
+ "b48ccce5d32e4c140de6-v0",
193
+ "b48ccce5d32e4c140de6-v1",
194
+ "b48ccce5d32e4c140de6-v2",
195
+ "b48ccce5d32e4c140de6-v3",
196
+ "d5b8361860cb4c246db4-v0",
197
+ "d5b8361860cb4c246db4-v1",
198
+ "d5b8361860cb4c246db4-v2",
199
+ "d5b8361860cb4c246db4-v3",
200
+ "0ab373eff89b242a8264-v0",
201
+ "0ab373eff89b242a8264-v1",
202
+ "0ab373eff89b242a8264-v2",
203
+ "0ab373eff89b242a8264-v3",
204
+ "5008f631374851f24ee1-v0",
205
+ "5008f631374851f24ee1-v1",
206
+ "5008f631374851f24ee1-v2",
207
+ "5008f631374851f24ee1-v3",
208
+ "20cc4d37f55fbf3ed7ee-v0",
209
+ "20cc4d37f55fbf3ed7ee-v1",
210
+ "20cc4d37f55fbf3ed7ee-v2",
211
+ "20cc4d37f55fbf3ed7ee-v3",
212
+ "01cdf02ead8b5e865381-v0",
213
+ "01cdf02ead8b5e865381-v1",
214
+ "01cdf02ead8b5e865381-v2",
215
+ "01cdf02ead8b5e865381-v3",
216
+ "2375d060b093b622ced0-v0",
217
+ "2375d060b093b622ced0-v1",
218
+ "2375d060b093b622ced0-v2",
219
+ "2375d060b093b622ced0-v3",
220
+ "b01c4cf0c7f7066d1630-v0",
221
+ "b01c4cf0c7f7066d1630-v1",
222
+ "b01c4cf0c7f7066d1630-v2",
223
+ "b01c4cf0c7f7066d1630-v3",
224
+ "6ff55fc61e7e5ee7e5cf-v0",
225
+ "6ff55fc61e7e5ee7e5cf-v1",
226
+ "6ff55fc61e7e5ee7e5cf-v2",
227
+ "6ff55fc61e7e5ee7e5cf-v3",
228
+ "01f73f72cedfa86e7b47-v0",
229
+ "01f73f72cedfa86e7b47-v1",
230
+ "01f73f72cedfa86e7b47-v2",
231
+ "01f73f72cedfa86e7b47-v3",
232
+ "b3d18f648ac9bb9154ec-v0",
233
+ "b3d18f648ac9bb9154ec-v1",
234
+ "b3d18f648ac9bb9154ec-v2",
235
+ "b3d18f648ac9bb9154ec-v3",
236
+ "a0ffe8c7cab33d01d46d-v0",
237
+ "a0ffe8c7cab33d01d46d-v1",
238
+ "a0ffe8c7cab33d01d46d-v2",
239
+ "a0ffe8c7cab33d01d46d-v3",
240
+ "abd072738608ed0853e0-v0",
241
+ "abd072738608ed0853e0-v1",
242
+ "abd072738608ed0853e0-v2",
243
+ "abd072738608ed0853e0-v3",
244
+ "854fdbac422e240a3bc0-v0",
245
+ "854fdbac422e240a3bc0-v1",
246
+ "854fdbac422e240a3bc0-v2",
247
+ "854fdbac422e240a3bc0-v3",
248
+ "c2827f8915f273e8b2bf-v0",
249
+ "c2827f8915f273e8b2bf-v1",
250
+ "c2827f8915f273e8b2bf-v2",
251
+ "c2827f8915f273e8b2bf-v3",
252
+ "1e4bee54e046daa3432e-v0",
253
+ "1e4bee54e046daa3432e-v1",
254
+ "1e4bee54e046daa3432e-v2",
255
+ "1e4bee54e046daa3432e-v3",
256
+ "df771458eda4d068489b-v0",
257
+ "df771458eda4d068489b-v1",
258
+ "df771458eda4d068489b-v2",
259
+ "df771458eda4d068489b-v3",
260
+ "9fd36b6629dbe456d618-v0",
261
+ "9fd36b6629dbe456d618-v1",
262
+ "9fd36b6629dbe456d618-v2",
263
+ "9fd36b6629dbe456d618-v3",
264
+ "54f1a71cfdb946aa1e22-v0",
265
+ "54f1a71cfdb946aa1e22-v1",
266
+ "54f1a71cfdb946aa1e22-v2",
267
+ "54f1a71cfdb946aa1e22-v3",
268
+ "8a49ade0408a3d33df2e-v0",
269
+ "8a49ade0408a3d33df2e-v1",
270
+ "8a49ade0408a3d33df2e-v2",
271
+ "8a49ade0408a3d33df2e-v3",
272
+ "aedd94f4737e2811b74f-v0",
273
+ "aedd94f4737e2811b74f-v1",
274
+ "aedd94f4737e2811b74f-v2",
275
+ "aedd94f4737e2811b74f-v3",
276
+ "7a6e5de02d94d4572241-v0",
277
+ "7a6e5de02d94d4572241-v1",
278
+ "7a6e5de02d94d4572241-v2",
279
+ "7a6e5de02d94d4572241-v3",
280
+ "fa70e030c60cf1584945-v0",
281
+ "fa70e030c60cf1584945-v1",
282
+ "fa70e030c60cf1584945-v2",
283
+ "fa70e030c60cf1584945-v3",
284
+ "9b364740f8d8b0b03982-v0",
285
+ "9b364740f8d8b0b03982-v1",
286
+ "9b364740f8d8b0b03982-v2",
287
+ "9b364740f8d8b0b03982-v3",
288
+ "d7c93bae8d9c53fd14bf-v0",
289
+ "d7c93bae8d9c53fd14bf-v1",
290
+ "d7c93bae8d9c53fd14bf-v2",
291
+ "d7c93bae8d9c53fd14bf-v3",
292
+ "236674029e7f3e236f31-v0",
293
+ "236674029e7f3e236f31-v1",
294
+ "236674029e7f3e236f31-v2",
295
+ "236674029e7f3e236f31-v3",
296
+ "5d5948fab5ea82417500-v0",
297
+ "5d5948fab5ea82417500-v1",
298
+ "5d5948fab5ea82417500-v2",
299
+ "5d5948fab5ea82417500-v3",
300
+ "20889edc9ef2a3944b36-v0",
301
+ "20889edc9ef2a3944b36-v1",
302
+ "20889edc9ef2a3944b36-v2",
303
+ "20889edc9ef2a3944b36-v3",
304
+ "e5754be7adb3f9a3c2d9-v0",
305
+ "e5754be7adb3f9a3c2d9-v1",
306
+ "e5754be7adb3f9a3c2d9-v2",
307
+ "e5754be7adb3f9a3c2d9-v3",
308
+ "95e77c467bddee4df460-v0",
309
+ "95e77c467bddee4df460-v1",
310
+ "95e77c467bddee4df460-v2",
311
+ "95e77c467bddee4df460-v3",
312
+ "63cd92c97338def9a9d2-v0",
313
+ "63cd92c97338def9a9d2-v1",
314
+ "63cd92c97338def9a9d2-v2",
315
+ "63cd92c97338def9a9d2-v3",
316
+ "aa8df97e47fd90c64457-v0",
317
+ "aa8df97e47fd90c64457-v1",
318
+ "aa8df97e47fd90c64457-v2",
319
+ "aa8df97e47fd90c64457-v3",
320
+ "aa5d1de39fbae4d7ad9e-v0",
321
+ "aa5d1de39fbae4d7ad9e-v1",
322
+ "aa5d1de39fbae4d7ad9e-v2",
323
+ "aa5d1de39fbae4d7ad9e-v3",
324
+ "cf5ce7483ba098f616f3-v0",
325
+ "cf5ce7483ba098f616f3-v1",
326
+ "cf5ce7483ba098f616f3-v2",
327
+ "cf5ce7483ba098f616f3-v3",
328
+ "3e7a80a6c89def99af8d-v0",
329
+ "3e7a80a6c89def99af8d-v1",
330
+ "3e7a80a6c89def99af8d-v2",
331
+ "3e7a80a6c89def99af8d-v3",
332
+ "bea94a0a0d57de031265-v0",
333
+ "bea94a0a0d57de031265-v1",
334
+ "bea94a0a0d57de031265-v2",
335
+ "bea94a0a0d57de031265-v3",
336
+ "e6f919123c1a9798050c-v0",
337
+ "e6f919123c1a9798050c-v1",
338
+ "e6f919123c1a9798050c-v2",
339
+ "e6f919123c1a9798050c-v3",
340
+ "9431e4b8c8fecb8af197-v0",
341
+ "9431e4b8c8fecb8af197-v1",
342
+ "9431e4b8c8fecb8af197-v2",
343
+ "9431e4b8c8fecb8af197-v3",
344
+ "8a5e2d5ee8a33fba9703-v0",
345
+ "8a5e2d5ee8a33fba9703-v1",
346
+ "8a5e2d5ee8a33fba9703-v2",
347
+ "8a5e2d5ee8a33fba9703-v3",
348
+ "6d71535d904c466c1bd4-v0",
349
+ "6d71535d904c466c1bd4-v1",
350
+ "6d71535d904c466c1bd4-v2",
351
+ "6d71535d904c466c1bd4-v3",
352
+ "08c8562106e171463cfa-v0",
353
+ "08c8562106e171463cfa-v1",
354
+ "08c8562106e171463cfa-v2",
355
+ "08c8562106e171463cfa-v3",
356
+ "5c925cef90b86cca85e7-v0",
357
+ "5c925cef90b86cca85e7-v1",
358
+ "5c925cef90b86cca85e7-v2",
359
+ "5c925cef90b86cca85e7-v3",
360
+ "dde147e8e1b1f215ab74-v0",
361
+ "dde147e8e1b1f215ab74-v1",
362
+ "dde147e8e1b1f215ab74-v2",
363
+ "dde147e8e1b1f215ab74-v3",
364
+ "23209c52d6f1e68bc1c4-v0",
365
+ "23209c52d6f1e68bc1c4-v1",
366
+ "23209c52d6f1e68bc1c4-v2",
367
+ "23209c52d6f1e68bc1c4-v3",
368
+ "72a88cda4d3320bc0b87-v0",
369
+ "72a88cda4d3320bc0b87-v1",
370
+ "72a88cda4d3320bc0b87-v2",
371
+ "72a88cda4d3320bc0b87-v3",
372
+ "91d6685f4c348b8aae5f-v0",
373
+ "91d6685f4c348b8aae5f-v1",
374
+ "91d6685f4c348b8aae5f-v2",
375
+ "91d6685f4c348b8aae5f-v3",
376
+ "f3df6b37e1b67fe2c3b0-v0",
377
+ "f3df6b37e1b67fe2c3b0-v1",
378
+ "f3df6b37e1b67fe2c3b0-v2",
379
+ "f3df6b37e1b67fe2c3b0-v3",
380
+ "bb204ad65ec97e0c1441-v0",
381
+ "bb204ad65ec97e0c1441-v1",
382
+ "bb204ad65ec97e0c1441-v2",
383
+ "bb204ad65ec97e0c1441-v3",
384
+ "42ca4d64703cfce58aab-v0",
385
+ "42ca4d64703cfce58aab-v1",
386
+ "42ca4d64703cfce58aab-v2",
387
+ "42ca4d64703cfce58aab-v3",
388
+ "1dbe996e85d24680ea52-v0",
389
+ "1dbe996e85d24680ea52-v1",
390
+ "1dbe996e85d24680ea52-v2",
391
+ "1dbe996e85d24680ea52-v3",
392
+ "7ae1ecae9954505ec431-v0",
393
+ "7ae1ecae9954505ec431-v1",
394
+ "7ae1ecae9954505ec431-v2",
395
+ "7ae1ecae9954505ec431-v3",
396
+ "e2739774f499722fab3c-v0",
397
+ "e2739774f499722fab3c-v1",
398
+ "e2739774f499722fab3c-v2",
399
+ "e2739774f499722fab3c-v3",
400
+ "455a3889e74a63dc4a7f-v0",
401
+ "455a3889e74a63dc4a7f-v1",
402
+ "455a3889e74a63dc4a7f-v2",
403
+ "455a3889e74a63dc4a7f-v3",
404
+ "266a4542856f979dc406-v0",
405
+ "266a4542856f979dc406-v1",
406
+ "266a4542856f979dc406-v2",
407
+ "266a4542856f979dc406-v3",
408
+ "bc5c99462af23c1ded8b-v0",
409
+ "bc5c99462af23c1ded8b-v1",
410
+ "bc5c99462af23c1ded8b-v2",
411
+ "bc5c99462af23c1ded8b-v3",
412
+ "e4747f32b34ac89e93bf-v0",
413
+ "e4747f32b34ac89e93bf-v1",
414
+ "e4747f32b34ac89e93bf-v2",
415
+ "e4747f32b34ac89e93bf-v3",
416
+ "d60a8e624048386e3094-v0",
417
+ "d60a8e624048386e3094-v1",
418
+ "d60a8e624048386e3094-v2",
419
+ "d60a8e624048386e3094-v3",
420
+ "3843fa0250d312b4f97c-v0",
421
+ "3843fa0250d312b4f97c-v1",
422
+ "3843fa0250d312b4f97c-v2",
423
+ "3843fa0250d312b4f97c-v3",
424
+ "422ece43bb9141c25714-v0",
425
+ "422ece43bb9141c25714-v1",
426
+ "422ece43bb9141c25714-v2",
427
+ "422ece43bb9141c25714-v3",
428
+ "d23622dd4eba6939a1f6-v0",
429
+ "d23622dd4eba6939a1f6-v1",
430
+ "d23622dd4eba6939a1f6-v2",
431
+ "d23622dd4eba6939a1f6-v3",
432
+ "b569a59ac13185b2c8ad-v0",
433
+ "b569a59ac13185b2c8ad-v1",
434
+ "b569a59ac13185b2c8ad-v2",
435
+ "b569a59ac13185b2c8ad-v3",
436
+ "2d820b7c3ee95f80d3f4-v0",
437
+ "2d820b7c3ee95f80d3f4-v1",
438
+ "2d820b7c3ee95f80d3f4-v2",
439
+ "2d820b7c3ee95f80d3f4-v3",
440
+ "5126b25a9bd271eb70c8-v0",
441
+ "5126b25a9bd271eb70c8-v1",
442
+ "5126b25a9bd271eb70c8-v2",
443
+ "5126b25a9bd271eb70c8-v3",
444
+ "4423c37b9342c83342e6-v0",
445
+ "4423c37b9342c83342e6-v1",
446
+ "4423c37b9342c83342e6-v2",
447
+ "4423c37b9342c83342e6-v3",
448
+ "a63c2eada3f5f7aa022e-v0",
449
+ "a63c2eada3f5f7aa022e-v1",
450
+ "a63c2eada3f5f7aa022e-v2",
451
+ "a63c2eada3f5f7aa022e-v3",
452
+ "3f562c2ba5dce1c08966-v0",
453
+ "3f562c2ba5dce1c08966-v1",
454
+ "3f562c2ba5dce1c08966-v2",
455
+ "3f562c2ba5dce1c08966-v3",
456
+ "0822237a043a73c1a4bc-v0",
457
+ "0822237a043a73c1a4bc-v1",
458
+ "0822237a043a73c1a4bc-v2",
459
+ "0822237a043a73c1a4bc-v3",
460
+ "3c22586a840d670b5e6c-v0",
461
+ "3c22586a840d670b5e6c-v1",
462
+ "3c22586a840d670b5e6c-v2",
463
+ "3c22586a840d670b5e6c-v3",
464
+ "c92a387d2882db431f65-v0",
465
+ "c92a387d2882db431f65-v1",
466
+ "c92a387d2882db431f65-v2",
467
+ "c92a387d2882db431f65-v3",
468
+ "d0afc9296f83fad5b52d-v0",
469
+ "d0afc9296f83fad5b52d-v1",
470
+ "d0afc9296f83fad5b52d-v2",
471
+ "d0afc9296f83fad5b52d-v3",
472
+ "d7ca171473b12143fdb4-v0",
473
+ "d7ca171473b12143fdb4-v1",
474
+ "d7ca171473b12143fdb4-v2",
475
+ "d7ca171473b12143fdb4-v3",
476
+ "92aa644c30a82c8729f0-v0",
477
+ "92aa644c30a82c8729f0-v1",
478
+ "92aa644c30a82c8729f0-v2",
479
+ "92aa644c30a82c8729f0-v3",
480
+ "917941dc4b82780cad3c-v0",
481
+ "917941dc4b82780cad3c-v1",
482
+ "917941dc4b82780cad3c-v2",
483
+ "917941dc4b82780cad3c-v3",
484
+ "76c5d77420d0adddfa9a-v0",
485
+ "76c5d77420d0adddfa9a-v1",
486
+ "76c5d77420d0adddfa9a-v2",
487
+ "76c5d77420d0adddfa9a-v3",
488
+ "26888c09438ca17387f8-v0",
489
+ "26888c09438ca17387f8-v1",
490
+ "26888c09438ca17387f8-v2",
491
+ "26888c09438ca17387f8-v3",
492
+ "db50fbd5e0fdb5ce3379-v0",
493
+ "db50fbd5e0fdb5ce3379-v1",
494
+ "db50fbd5e0fdb5ce3379-v2",
495
+ "db50fbd5e0fdb5ce3379-v3",
496
+ "6a5aef795e625c1fb2a9-v0",
497
+ "6a5aef795e625c1fb2a9-v1",
498
+ "6a5aef795e625c1fb2a9-v2",
499
+ "6a5aef795e625c1fb2a9-v3",
500
+ "7ff8979556221a260ce0-v0",
501
+ "7ff8979556221a260ce0-v1",
502
+ "7ff8979556221a260ce0-v2",
503
+ "7ff8979556221a260ce0-v3",
504
+ "b4b0d309012ca95a2c4a-v0",
505
+ "b4b0d309012ca95a2c4a-v1",
506
+ "b4b0d309012ca95a2c4a-v2",
507
+ "b4b0d309012ca95a2c4a-v3",
508
+ "8d1c41e250d29d3bd13c-v0",
509
+ "8d1c41e250d29d3bd13c-v1",
510
+ "8d1c41e250d29d3bd13c-v2",
511
+ "8d1c41e250d29d3bd13c-v3",
512
+ "fd8ee32090590934a302-v0",
513
+ "fd8ee32090590934a302-v1",
514
+ "fd8ee32090590934a302-v2",
515
+ "fd8ee32090590934a302-v3",
516
+ "e94bfdac64dd4fd538e8-v0",
517
+ "e94bfdac64dd4fd538e8-v1",
518
+ "e94bfdac64dd4fd538e8-v2",
519
+ "e94bfdac64dd4fd538e8-v3",
520
+ "0c08cc218664cf71d7a4-v0",
521
+ "0c08cc218664cf71d7a4-v1",
522
+ "0c08cc218664cf71d7a4-v2",
523
+ "0c08cc218664cf71d7a4-v3",
524
+ "d0327acf7b451f652464-v0",
525
+ "d0327acf7b451f652464-v1",
526
+ "d0327acf7b451f652464-v2",
527
+ "d0327acf7b451f652464-v3",
528
+ "67ce375b4b7a7236f8c4-v0",
529
+ "67ce375b4b7a7236f8c4-v1",
530
+ "67ce375b4b7a7236f8c4-v2",
531
+ "67ce375b4b7a7236f8c4-v3",
532
+ "cfce2cfcf8317763dd44-v0",
533
+ "cfce2cfcf8317763dd44-v1",
534
+ "cfce2cfcf8317763dd44-v2",
535
+ "cfce2cfcf8317763dd44-v3",
536
+ "7918b36c98ee56019646-v0",
537
+ "7918b36c98ee56019646-v1",
538
+ "7918b36c98ee56019646-v2",
539
+ "7918b36c98ee56019646-v3",
540
+ "5763f942e73f616782ad-v0",
541
+ "5763f942e73f616782ad-v1",
542
+ "5763f942e73f616782ad-v2",
543
+ "5763f942e73f616782ad-v3",
544
+ "4ce0b2318a0b65abd68b-v0",
545
+ "4ce0b2318a0b65abd68b-v1",
546
+ "4ce0b2318a0b65abd68b-v2",
547
+ "4ce0b2318a0b65abd68b-v3",
548
+ "9f0d8b1ae1f1330141d3-v0",
549
+ "9f0d8b1ae1f1330141d3-v1",
550
+ "9f0d8b1ae1f1330141d3-v2",
551
+ "9f0d8b1ae1f1330141d3-v3",
552
+ "bb8116ecf2320e27daa5-v0",
553
+ "bb8116ecf2320e27daa5-v1",
554
+ "bb8116ecf2320e27daa5-v2",
555
+ "bb8116ecf2320e27daa5-v3",
556
+ "03c02d7470d5a1fab0e5-v0",
557
+ "03c02d7470d5a1fab0e5-v1",
558
+ "03c02d7470d5a1fab0e5-v2",
559
+ "03c02d7470d5a1fab0e5-v3",
560
+ "8a77346173b288a90775-v0",
561
+ "8a77346173b288a90775-v1",
562
+ "8a77346173b288a90775-v2",
563
+ "8a77346173b288a90775-v3",
564
+ "d3c48fb9f017b94b8508-v0",
565
+ "d3c48fb9f017b94b8508-v1",
566
+ "d3c48fb9f017b94b8508-v2",
567
+ "d3c48fb9f017b94b8508-v3",
568
+ "32cd7da01c4e542ad376-v0",
569
+ "32cd7da01c4e542ad376-v1",
570
+ "32cd7da01c4e542ad376-v2",
571
+ "32cd7da01c4e542ad376-v3",
572
+ "5116a7c03161b9a8e33e-v0",
573
+ "5116a7c03161b9a8e33e-v1",
574
+ "5116a7c03161b9a8e33e-v2",
575
+ "5116a7c03161b9a8e33e-v3",
576
+ "3707f2f19bd7ee197669-v0",
577
+ "3707f2f19bd7ee197669-v1",
578
+ "3707f2f19bd7ee197669-v2",
579
+ "3707f2f19bd7ee197669-v3",
580
+ "e6c8b873036ef3cff206-v0",
581
+ "e6c8b873036ef3cff206-v1",
582
+ "e6c8b873036ef3cff206-v2",
583
+ "e6c8b873036ef3cff206-v3",
584
+ "55c986292a19ad365d72-v0",
585
+ "55c986292a19ad365d72-v1",
586
+ "55c986292a19ad365d72-v2",
587
+ "55c986292a19ad365d72-v3",
588
+ "032a127f294a9ccc4046-v0",
589
+ "032a127f294a9ccc4046-v1",
590
+ "032a127f294a9ccc4046-v2",
591
+ "032a127f294a9ccc4046-v3",
592
+ "2d03806613e95eda2c5d-v0",
593
+ "2d03806613e95eda2c5d-v1",
594
+ "2d03806613e95eda2c5d-v2",
595
+ "2d03806613e95eda2c5d-v3",
596
+ "1e0b5a590c8169b226d1-v0",
597
+ "1e0b5a590c8169b226d1-v1",
598
+ "1e0b5a590c8169b226d1-v2",
599
+ "1e0b5a590c8169b226d1-v3",
600
+ "899bb8c3073520a2d786-v0",
601
+ "899bb8c3073520a2d786-v1",
602
+ "899bb8c3073520a2d786-v2",
603
+ "899bb8c3073520a2d786-v3",
604
+ "45a047f3468ca0abbfd6-v0",
605
+ "45a047f3468ca0abbfd6-v1",
606
+ "45a047f3468ca0abbfd6-v2",
607
+ "45a047f3468ca0abbfd6-v3",
608
+ "cce70d8f1cf989863f09-v0",
609
+ "cce70d8f1cf989863f09-v1",
610
+ "cce70d8f1cf989863f09-v2",
611
+ "cce70d8f1cf989863f09-v3",
612
+ "02214738c17052c83f0d-v0",
613
+ "02214738c17052c83f0d-v1",
614
+ "02214738c17052c83f0d-v2",
615
+ "02214738c17052c83f0d-v3",
616
+ "093fad17e65696cf48e2-v0",
617
+ "093fad17e65696cf48e2-v1",
618
+ "093fad17e65696cf48e2-v2",
619
+ "093fad17e65696cf48e2-v3",
620
+ "60942606bb00f6896d04-v0",
621
+ "60942606bb00f6896d04-v1",
622
+ "60942606bb00f6896d04-v2",
623
+ "60942606bb00f6896d04-v3",
624
+ "53a4c2377d09fa43c136-v0",
625
+ "53a4c2377d09fa43c136-v1",
626
+ "53a4c2377d09fa43c136-v2",
627
+ "53a4c2377d09fa43c136-v3",
628
+ "38822653da041f7b0bcc-v0",
629
+ "38822653da041f7b0bcc-v1",
630
+ "38822653da041f7b0bcc-v2",
631
+ "38822653da041f7b0bcc-v3",
632
+ "83331db654f927bba7db-v0",
633
+ "83331db654f927bba7db-v1",
634
+ "83331db654f927bba7db-v2",
635
+ "83331db654f927bba7db-v3",
636
+ "0d6b8870f9f8fbecd06a-v0",
637
+ "0d6b8870f9f8fbecd06a-v1",
638
+ "0d6b8870f9f8fbecd06a-v2",
639
+ "0d6b8870f9f8fbecd06a-v3",
640
+ "efd33140ce57a557e922-v0",
641
+ "efd33140ce57a557e922-v1",
642
+ "efd33140ce57a557e922-v2",
643
+ "efd33140ce57a557e922-v3",
644
+ "e6c9829ce1dcf43fd8e6-v0",
645
+ "e6c9829ce1dcf43fd8e6-v1",
646
+ "e6c9829ce1dcf43fd8e6-v2",
647
+ "e6c9829ce1dcf43fd8e6-v3",
648
+ "d6180603ae0451879c14-v0",
649
+ "d6180603ae0451879c14-v1",
650
+ "d6180603ae0451879c14-v2",
651
+ "d6180603ae0451879c14-v3",
652
+ "08742aac51091be0a9c6-v0",
653
+ "08742aac51091be0a9c6-v1",
654
+ "08742aac51091be0a9c6-v2",
655
+ "08742aac51091be0a9c6-v3",
656
+ "170540b624f3109b3fd0-v0",
657
+ "170540b624f3109b3fd0-v1",
658
+ "170540b624f3109b3fd0-v2",
659
+ "170540b624f3109b3fd0-v3",
660
+ "ef66425f8d0214644d8d-v0",
661
+ "ef66425f8d0214644d8d-v1",
662
+ "ef66425f8d0214644d8d-v2",
663
+ "ef66425f8d0214644d8d-v3",
664
+ "a764131510993a8e2ca2-v0",
665
+ "a764131510993a8e2ca2-v1",
666
+ "a764131510993a8e2ca2-v2",
667
+ "a764131510993a8e2ca2-v3",
668
+ "1555d7f97f52214e121d-v0",
669
+ "1555d7f97f52214e121d-v1",
670
+ "1555d7f97f52214e121d-v2",
671
+ "1555d7f97f52214e121d-v3",
672
+ "57833fdb2b7db066c37f-v0",
673
+ "57833fdb2b7db066c37f-v1",
674
+ "57833fdb2b7db066c37f-v2",
675
+ "57833fdb2b7db066c37f-v3",
676
+ "f8834e3399d85f499155-v0",
677
+ "f8834e3399d85f499155-v1",
678
+ "f8834e3399d85f499155-v2",
679
+ "f8834e3399d85f499155-v3",
680
+ "0ab36e04e97c91000199-v0",
681
+ "0ab36e04e97c91000199-v1",
682
+ "0ab36e04e97c91000199-v2",
683
+ "0ab36e04e97c91000199-v3",
684
+ "18424f2430fb7a69602d-v0",
685
+ "18424f2430fb7a69602d-v1",
686
+ "18424f2430fb7a69602d-v2",
687
+ "18424f2430fb7a69602d-v3",
688
+ "178675742d6486f51bc4-v0",
689
+ "178675742d6486f51bc4-v1",
690
+ "178675742d6486f51bc4-v2",
691
+ "178675742d6486f51bc4-v3",
692
+ "01c0b332d3789803ba65-v0",
693
+ "01c0b332d3789803ba65-v1",
694
+ "01c0b332d3789803ba65-v2",
695
+ "01c0b332d3789803ba65-v3"
696
+ ],
697
+ "val_ids": [
698
+ "490d93c0b080175b8a66-v0",
699
+ "490d93c0b080175b8a66-v1",
700
+ "490d93c0b080175b8a66-v2",
701
+ "490d93c0b080175b8a66-v3",
702
+ "eb03c5c9f2711fe9b9ec-v0",
703
+ "eb03c5c9f2711fe9b9ec-v1",
704
+ "eb03c5c9f2711fe9b9ec-v2",
705
+ "eb03c5c9f2711fe9b9ec-v3",
706
+ "c42cdd597515d930619f-v0",
707
+ "c42cdd597515d930619f-v1",
708
+ "c42cdd597515d930619f-v2",
709
+ "c42cdd597515d930619f-v3",
710
+ "9834d7b46708c5f3141a-v0",
711
+ "9834d7b46708c5f3141a-v1",
712
+ "9834d7b46708c5f3141a-v2",
713
+ "9834d7b46708c5f3141a-v3",
714
+ "4a1770b349d1696431dd-v0",
715
+ "4a1770b349d1696431dd-v1",
716
+ "4a1770b349d1696431dd-v2",
717
+ "4a1770b349d1696431dd-v3",
718
+ "a79a0ab110691491fed2-v0",
719
+ "a79a0ab110691491fed2-v1",
720
+ "a79a0ab110691491fed2-v2",
721
+ "a79a0ab110691491fed2-v3",
722
+ "e29f72f0f7fa3b880741-v0",
723
+ "e29f72f0f7fa3b880741-v1",
724
+ "e29f72f0f7fa3b880741-v2",
725
+ "e29f72f0f7fa3b880741-v3",
726
+ "a19bf682317a9d72d332-v0",
727
+ "a19bf682317a9d72d332-v1",
728
+ "a19bf682317a9d72d332-v2",
729
+ "a19bf682317a9d72d332-v3",
730
+ "8cde9d38a1e42442f821-v0",
731
+ "8cde9d38a1e42442f821-v1",
732
+ "8cde9d38a1e42442f821-v2",
733
+ "8cde9d38a1e42442f821-v3",
734
+ "fe9c7b60406ed4fd1983-v0",
735
+ "fe9c7b60406ed4fd1983-v1",
736
+ "fe9c7b60406ed4fd1983-v2",
737
+ "fe9c7b60406ed4fd1983-v3",
738
+ "e4dbd8299958bb3b0b9e-v0",
739
+ "e4dbd8299958bb3b0b9e-v1",
740
+ "e4dbd8299958bb3b0b9e-v2",
741
+ "e4dbd8299958bb3b0b9e-v3",
742
+ "48bbd01188ea983a5b2f-v0",
743
+ "48bbd01188ea983a5b2f-v1",
744
+ "48bbd01188ea983a5b2f-v2",
745
+ "48bbd01188ea983a5b2f-v3",
746
+ "3228102524c244aa3d59-v0",
747
+ "3228102524c244aa3d59-v1",
748
+ "3228102524c244aa3d59-v2",
749
+ "3228102524c244aa3d59-v3",
750
+ "5896b3c215ed8167d0cc-v0",
751
+ "5896b3c215ed8167d0cc-v1",
752
+ "5896b3c215ed8167d0cc-v2",
753
+ "5896b3c215ed8167d0cc-v3",
754
+ "8d36fc4f31a77b9eab9f-v0",
755
+ "8d36fc4f31a77b9eab9f-v1",
756
+ "8d36fc4f31a77b9eab9f-v2",
757
+ "8d36fc4f31a77b9eab9f-v3",
758
+ "dcdf4504811d49eed613-v0",
759
+ "dcdf4504811d49eed613-v1",
760
+ "dcdf4504811d49eed613-v2",
761
+ "dcdf4504811d49eed613-v3",
762
+ "f4bebfef2755063c3327-v0",
763
+ "f4bebfef2755063c3327-v1",
764
+ "f4bebfef2755063c3327-v2",
765
+ "f4bebfef2755063c3327-v3",
766
+ "421edd1b4af56929a114-v0",
767
+ "421edd1b4af56929a114-v1",
768
+ "421edd1b4af56929a114-v2",
769
+ "421edd1b4af56929a114-v3",
770
+ "0c351b2db97c91e18d91-v0",
771
+ "0c351b2db97c91e18d91-v1",
772
+ "0c351b2db97c91e18d91-v2",
773
+ "0c351b2db97c91e18d91-v3",
774
+ "178d10126082a9678b0f-v0",
775
+ "178d10126082a9678b0f-v1",
776
+ "178d10126082a9678b0f-v2",
777
+ "178d10126082a9678b0f-v3",
778
+ "28fd97ba3f683c2a99c5-v0",
779
+ "28fd97ba3f683c2a99c5-v1",
780
+ "28fd97ba3f683c2a99c5-v2",
781
+ "28fd97ba3f683c2a99c5-v3",
782
+ "226b1903a8c86d5f49a8-v0",
783
+ "226b1903a8c86d5f49a8-v1",
784
+ "226b1903a8c86d5f49a8-v2",
785
+ "226b1903a8c86d5f49a8-v3",
786
+ "8d343b01a4d96fce0c93-v0",
787
+ "8d343b01a4d96fce0c93-v1",
788
+ "8d343b01a4d96fce0c93-v2",
789
+ "8d343b01a4d96fce0c93-v3",
790
+ "81b217a8060cb30b40ae-v0",
791
+ "81b217a8060cb30b40ae-v1",
792
+ "81b217a8060cb30b40ae-v2",
793
+ "81b217a8060cb30b40ae-v3",
794
+ "2fac0c5475e8e927f3f8-v0",
795
+ "2fac0c5475e8e927f3f8-v1",
796
+ "2fac0c5475e8e927f3f8-v2",
797
+ "2fac0c5475e8e927f3f8-v3",
798
+ "81ad71ac6fd6f8101cec-v0",
799
+ "81ad71ac6fd6f8101cec-v1",
800
+ "81ad71ac6fd6f8101cec-v2",
801
+ "81ad71ac6fd6f8101cec-v3",
802
+ "0ba4d03f17809d59a303-v0",
803
+ "0ba4d03f17809d59a303-v1",
804
+ "0ba4d03f17809d59a303-v2",
805
+ "0ba4d03f17809d59a303-v3",
806
+ "33391b3c51b93088937f-v0",
807
+ "33391b3c51b93088937f-v1",
808
+ "33391b3c51b93088937f-v2",
809
+ "33391b3c51b93088937f-v3",
810
+ "02b0826239e05c2c97d4-v0",
811
+ "02b0826239e05c2c97d4-v1",
812
+ "02b0826239e05c2c97d4-v2",
813
+ "02b0826239e05c2c97d4-v3",
814
+ "59b5ec716c803703ea5d-v0",
815
+ "59b5ec716c803703ea5d-v1",
816
+ "59b5ec716c803703ea5d-v2",
817
+ "59b5ec716c803703ea5d-v3",
818
+ "f6b970010bc3833bd256-v0",
819
+ "f6b970010bc3833bd256-v1",
820
+ "f6b970010bc3833bd256-v2",
821
+ "f6b970010bc3833bd256-v3",
822
+ "3577c750b03d138cb1c3-v0",
823
+ "3577c750b03d138cb1c3-v1",
824
+ "3577c750b03d138cb1c3-v2",
825
+ "3577c750b03d138cb1c3-v3",
826
+ "1ad605ceb804ef551c30-v0",
827
+ "1ad605ceb804ef551c30-v1",
828
+ "1ad605ceb804ef551c30-v2",
829
+ "1ad605ceb804ef551c30-v3",
830
+ "90df3e622026c10b2314-v0",
831
+ "90df3e622026c10b2314-v1",
832
+ "90df3e622026c10b2314-v2",
833
+ "90df3e622026c10b2314-v3",
834
+ "80b7421276b7b7aa42d2-v0",
835
+ "80b7421276b7b7aa42d2-v1",
836
+ "80b7421276b7b7aa42d2-v2",
837
+ "80b7421276b7b7aa42d2-v3",
838
+ "e892cd1da4e97cbb3156-v0",
839
+ "e892cd1da4e97cbb3156-v1",
840
+ "e892cd1da4e97cbb3156-v2",
841
+ "e892cd1da4e97cbb3156-v3",
842
+ "08316a144698ca3c94fd-v0",
843
+ "08316a144698ca3c94fd-v1",
844
+ "08316a144698ca3c94fd-v2",
845
+ "08316a144698ca3c94fd-v3",
846
+ "ca8462cb5c9b32b9a8d5-v0",
847
+ "ca8462cb5c9b32b9a8d5-v1",
848
+ "ca8462cb5c9b32b9a8d5-v2",
849
+ "ca8462cb5c9b32b9a8d5-v3",
850
+ "89e5abe091880bdda1eb-v0",
851
+ "89e5abe091880bdda1eb-v1",
852
+ "89e5abe091880bdda1eb-v2",
853
+ "89e5abe091880bdda1eb-v3",
854
+ "de83ba5f5f6e234b2cf5-v0",
855
+ "de83ba5f5f6e234b2cf5-v1",
856
+ "de83ba5f5f6e234b2cf5-v2",
857
+ "de83ba5f5f6e234b2cf5-v3",
858
+ "8e5b9b5a46d3351bf52c-v0",
859
+ "8e5b9b5a46d3351bf52c-v1",
860
+ "8e5b9b5a46d3351bf52c-v2",
861
+ "8e5b9b5a46d3351bf52c-v3",
862
+ "60222ffa34fbeaf3487f-v0",
863
+ "60222ffa34fbeaf3487f-v1",
864
+ "60222ffa34fbeaf3487f-v2",
865
+ "60222ffa34fbeaf3487f-v3",
866
+ "460e87ec477c804638b3-v0",
867
+ "460e87ec477c804638b3-v1",
868
+ "460e87ec477c804638b3-v2",
869
+ "460e87ec477c804638b3-v3",
870
+ "07275900f72e95c1f69c-v0",
871
+ "07275900f72e95c1f69c-v1",
872
+ "07275900f72e95c1f69c-v2",
873
+ "07275900f72e95c1f69c-v3",
874
+ "bb5377db3c418476576d-v0",
875
+ "bb5377db3c418476576d-v1",
876
+ "bb5377db3c418476576d-v2",
877
+ "bb5377db3c418476576d-v3"
878
+ ],
879
+ "parameters": 306321,
880
+ "teachers": [
881
+ "mesh-proxy:allenai/objaverse",
882
+ "mesh-proxy:scenario-labs/3d-collection"
883
+ ],
884
+ "validation": "held-out objects",
885
+ "manifest_sha256": "47c6cb23929321cfc5b21a16807f6a460d18d0231f11c6afd7136fb4b6bea945",
886
+ "initialization": {
887
+ "sha256": "83c065d2a50cf086928542cf8d40dc4416b379e38b9aa61d842dc9106d4b3306",
888
+ "config": {
889
+ "grid": 16,
890
+ "width": 64
891
+ },
892
+ "step": 4800,
893
+ "optimizer": "fresh AdamW; weights-only post-training"
894
+ },
895
+ "steps_requested": 8000,
896
+ "learning_rate": 5e-05,
897
+ "batch_size": 2,
898
+ "validation_fraction": 0.2,
899
+ "baseline_validation": {
900
+ "occupancy": 0.45328883288635147,
901
+ "position": 0.0943834915343258,
902
+ "colour": 0.03328401494266776,
903
+ "scale": 0.002456185630742564,
904
+ "rotation": 0.00010184630525853007,
905
+ "opacity": 0.00022721136727644383,
906
+ "geometry": 0.0,
907
+ "loss": 0.6147233684857686,
908
+ "occupancy_iou": 0.2426499036554661
909
+ },
910
+ "base_model": "Lucasgitguy-co/forma3d-tiny",
911
+ "base_revision": "e80b8d895cc2e405894473abe4ce40b3472eaca7",
912
+ "training_method": "weights-only continuation; fresh AdamW",
913
+ "known_v1_assets_excluded": true
914
+ }