Publish evaluated Forma3D Tiny 3 ABO continuation
Browse files- ATTRIBUTIONS.json +0 -0
- LICENSE +202 -0
- NOTICE +13 -0
- README.md +67 -0
- SHA256SUMS +34 -0
- THIRD_PARTY.md +13 -0
- completed.json +5 -0
- config.json +4 -0
- environment.txt +28 -0
- evaluation.json +41 -0
- licenses/Idol-APACHE-2.0.txt +202 -0
- licenses/LICENSE-ABO-CC-BY-4.0.txt +317 -0
- metrics.jsonl +24 -0
- model.pt +3 -0
- preparation.json +23 -0
- pyproject.toml +28 -0
- recipe/decode.mjs +13 -0
- recipe/package-lock.json +784 -0
- recipe/package.json +1 -0
- recipe/pipeline.py +581 -0
- recipe/requirements.txt +8 -0
- recipe/run.command +27 -0
- settings.json +12 -0
- splits/test.jsonl +0 -0
- splits/train.jsonl +0 -0
- splits/validation.jsonl +0 -0
- src/image3d_student/__init__.py +5 -0
- src/image3d_student/cli.py +126 -0
- src/image3d_student/data.py +149 -0
- src/image3d_student/demo.py +31 -0
- src/image3d_student/engine.py +204 -0
- src/image3d_student/model.py +76 -0
- src/image3d_student/splats.py +108 -0
- src/image3d_student/teacher_idol.py +141 -0
- training.json +52 -0
ATTRIBUTIONS.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
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,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 12 |
+
|
| 13 |
+
ABO continuation data: Amazon.com, Amazon Berkeley Objects contributors; converted to GLB by CodePawl. CC BY 4.0. Training preprocessing decompresses geometry, normalizes objects, samples surface colors and renders Gaussian proxies. See ATTRIBUTIONS.json and licenses/LICENSE-ABO-CC-BY-4.0.txt. No endorsement implied.
|
README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
pipeline_tag: image-to-3d
|
| 4 |
+
base_model: Lucasgitguy-co/forma3d-tiny-2
|
| 5 |
+
base_model_relation: finetune
|
| 6 |
+
model_name: Forma3D Tiny 3
|
| 7 |
+
datasets:
|
| 8 |
+
- codepawl/abo-glb
|
| 9 |
+
tags:
|
| 10 |
+
- pytorch
|
| 11 |
+
- gaussian-splatting
|
| 12 |
+
- mesh-proxy-supervision
|
| 13 |
+
- research-prototype
|
| 14 |
+
---
|
| 15 |
+
# Forma3D Tiny 3
|
| 16 |
+
|
| 17 |
+
Continued from [Lucasgitguy-co/forma3d-tiny-2](https://huggingface.co/Lucasgitguy-co/forma3d-tiny-2) at `1648582b07a0f7f9d482a8c0f4cf89e6ecd86972`.
|
| 18 |
+
Same 306,321-parameter architecture, 16 cubed grid, width 64, maximum 4,096 Gaussian slots.
|
| 19 |
+
Fresh AdamW optimizer at the start of continuation. Completed 6000 steps;
|
| 20 |
+
selected step 6000 by validation loss only.
|
| 21 |
+
|
| 22 |
+
## Data and scope
|
| 23 |
+
|
| 24 |
+
512 ABO objects, 2048 orthographic Gaussian-proxy images on black.
|
| 25 |
+
Data revision: `c7c8293fed3b7ab122ef5d7896521a187e0962d7`. Targets are isotropic mesh surface samples, compressed to
|
| 26 |
+
one Gaussian per occupied grid cell. Texture surface colors are sampled; full PBR rendering
|
| 27 |
+
and differentiable rendering are not used. No natural-photo training is claimed.
|
| 28 |
+
MeshAnythingV2, Idol Diffusers, SHARP, Pixal3D, Idol Turbo and Qwen3.5-4B-3D-Coder
|
| 29 |
+
were **not run, merged, or distilled** in this Mac-only recipe.
|
| 30 |
+
|
| 31 |
+
Objects, shared Amazon item IDs and exact mesh/image/target hashes are grouped before splitting.
|
| 32 |
+
Split groups: {"train": 348, "validation": 87, "test": 77}. Test data is excluded from training and checkpoint selection.
|
| 33 |
+
Near duplicates can remain; overlap with ancestor training datasets is not fully known.
|
| 34 |
+
|
| 35 |
+
## Evaluation
|
| 36 |
+
|
| 37 |
+
Both checkpoints were evaluated on the identical held-out ABO proxy views.
|
| 38 |
+
|
| 39 |
+
| Metric | Starting v2 | Selected v3 |
|
| 40 |
+
|---|---:|---:|
|
| 41 |
+
| Mean test loss | 0.690551 | 0.544487 |
|
| 42 |
+
| Mean view occupancy IoU, threshold 0.5 | 0.286866 | 0.339860 |
|
| 43 |
+
|
| 44 |
+
These measurements do not establish natural-photo reconstruction, photorealism, mesh quality,
|
| 45 |
+
novel-view rendering quality or improvement on v2's original domains. No confidence intervals
|
| 46 |
+
or broad generalization claim. See evaluation.json, preparation.json and split manifests.
|
| 47 |
+
|
| 48 |
+
## Use
|
| 49 |
+
|
| 50 |
+
With Python 3.11, install this repository using `pip install .`, then:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
image3d generate --checkpoint model.pt --image centered-object-on-black.png --out object.ply --device mps
|
| 54 |
+
image3d preview --ply object.ply --out preview.png
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
Output is Gaussian PLY, not a textured GLB mesh. Inputs should match centered objects on black.
|
| 58 |
+
The model has no text prompt conditioning. Reproduction launcher and locked dependencies are in recipe/.
|
| 59 |
+
|
| 60 |
+
## Attribution
|
| 61 |
+
|
| 62 |
+
Publisher source and weights: Apache 2.0. Retain inherited NOTICE, THIRD_PARTY.md and ATTRIBUTIONS.json.
|
| 63 |
+
ABO models, images and listing text: Amazon.com and the Amazon Berkeley Objects contributors,
|
| 64 |
+
CC BY 4.0; converted to GLB by CodePawl. The recipe further decompresses geometry,
|
| 65 |
+
normalizes coordinates, samples colored surfaces and renders proxy images.
|
| 66 |
+
See [ABO dataset](https://huggingface.co/datasets/codepawl/abo-glb),
|
| 67 |
+
ATTRIBUTIONS.json and licenses/LICENSE-ABO-CC-BY-4.0.txt. Raw assets are not bundled.
|
SHA256SUMS
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
952b78774f6d41182b685271d6ed64abbca9b0c5ff4081e8e55ab92ce256e737 ATTRIBUTIONS.json
|
| 2 |
+
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
|
| 3 |
+
d84c0de5fa230ed94d4a2c84c791ffe4a13a2f73ac2657b19bcf0b55f11726a7 NOTICE
|
| 4 |
+
0ae2c6c30c34fd70281e576023e040052c7546adad85c530c91e179299c2dc16 README.md
|
| 5 |
+
ef7c689f184a7921b02887eeb8d859e8cca77da6d2082999bb19bf29beda61f1 THIRD_PARTY.md
|
| 6 |
+
54439a905e24b39dfc4d2c28a4b45b97e584a3cf40c612700e71aba4b51d9409 completed.json
|
| 7 |
+
25d53c6500d9e45aca8a2ce1fe54030e05a0822c554ab24cfcbfeaa631ee9c91 config.json
|
| 8 |
+
e6aca692ff4fe1a6066c67152253435bebb86bcd33983efa73d9b45b0709da23 environment.txt
|
| 9 |
+
5bab5e4f1938a873a5f2eb8405fe4d94e0b532bc566250189c5b1e764ed3eebf evaluation.json
|
| 10 |
+
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 licenses/Idol-APACHE-2.0.txt
|
| 11 |
+
419896aea50c15d6e40c5b4baf4bd346f78223b9a354c7357ad00262afdc08ec licenses/LICENSE-ABO-CC-BY-4.0.txt
|
| 12 |
+
c9175419fd345f1b2831201495cf27d9929b92dee52288c798ece243b13f6db1 metrics.jsonl
|
| 13 |
+
ec7c37e8ff293877149822150a5e08e8dde3e37fe0e6cfc1dc71e05adecd7790 model.pt
|
| 14 |
+
14d89f03bdae1c5cb416b121248d024a49c30d5545bd560b6b390c52afa049c2 preparation.json
|
| 15 |
+
e8f66f2a90fbe6c42cc098357cc862d8ea00eaf3a4f38fa22eb59b168eab2ee9 pyproject.toml
|
| 16 |
+
da5bb78d178599428987111fc83e27678fd3589e59c4756653f612114e139a29 recipe/decode.mjs
|
| 17 |
+
72730d8990cffc36b3f77ced370306978d01ddeb9b417c891613c0e95cb475ac recipe/package-lock.json
|
| 18 |
+
4d87fdff2312cb3afc1ecd98c39e152c6b44e792b4ea717c8f94204ab8d857a7 recipe/package.json
|
| 19 |
+
33ef603b3550a78eb982138540bcdbbca596d3b3f697368593a6d5ca3ff4b436 recipe/pipeline.py
|
| 20 |
+
890fa9075ec44ab44d42db01a893df1fd1ab8b6c989324041f4ee3537a2d5720 recipe/requirements.txt
|
| 21 |
+
b9e8acb85f25b4e35ab555e81d09a3679e5daa4edafdc5f28a80c4442da83db3 recipe/run.command
|
| 22 |
+
2d0c28622dfb8d3a51f1cc631bbb62ed48c135c83aee2b9edf72aed161b9a3e3 settings.json
|
| 23 |
+
c8f927c42ba2ccecd960536de8c7c7431c8fe674401e1fbe7d7613d18cab860d splits/test.jsonl
|
| 24 |
+
65af1c16cefde7e9cd411af055ccdee36c183382db18a4fd9a9d4ea82fb248ee splits/train.jsonl
|
| 25 |
+
65ca208e5b7d59d398056c997751b12a18ce05c9361e37f4160f3b1420cbe52a splits/validation.jsonl
|
| 26 |
+
c09ce8cb78cd68ae32b618ba49f97508b6ce14bc89e88c5b80cc3e2487bd919c src/image3d_student/__init__.py
|
| 27 |
+
b4be53a864458a84009e91277ff64767dffc6139ca3afcc50526747743722c02 src/image3d_student/cli.py
|
| 28 |
+
d34f9fa50d574764e5e2c1c31c1ca477f2bc4a1887e103c5c47b754f1f1d8c72 src/image3d_student/data.py
|
| 29 |
+
e01d1b42929c993d92cad1bde8944b14c6a8e325fc279d8fd3e9c7287523ad69 src/image3d_student/demo.py
|
| 30 |
+
7335dcc99fcd9cea5c217841376f0ca8c0ca179809b4df34e344bf929d11da51 src/image3d_student/engine.py
|
| 31 |
+
a18005f75a66846446b2574b4f1b085ebde5321e835c4e5b4507e15e812ee5ef src/image3d_student/model.py
|
| 32 |
+
41961fae62d2819c1e3f455e74539a66b8c4f57bedd9fb9379497b3a5764c692 src/image3d_student/splats.py
|
| 33 |
+
2e2439bc0a48c52e32a8e273f879ed7328fdb82632a996ed3e239e0391ca3a59 src/image3d_student/teacher_idol.py
|
| 34 |
+
ebe640c6078f02ef84fb9992bd3f7585a326b523e9f480526c2057d5a2892074 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.
|
completed.json
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"completed_steps": 6000,
|
| 3 |
+
"selected_step": 6000,
|
| 4 |
+
"best_validation_loss": 0.5845230878598389
|
| 5 |
+
}
|
config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"grid": 16,
|
| 3 |
+
"width": 64
|
| 4 |
+
}
|
environment.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Jinja2==3.1.6
|
| 2 |
+
MarkupSafe==3.0.4
|
| 3 |
+
PyYAML==6.0.3
|
| 4 |
+
anyio==4.16.0
|
| 5 |
+
click==8.5.1
|
| 6 |
+
filelock==4.2.9
|
| 7 |
+
fsspec==2026.9.0
|
| 8 |
+
h11==0.16.0
|
| 9 |
+
hf-xet==1.8.0
|
| 10 |
+
httpcore2==2.16.0
|
| 11 |
+
httpx2==2.16.0
|
| 12 |
+
huggingface_hub==2.4.0
|
| 13 |
+
idna==3.20
|
| 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 |
+
plyfile==1.1.5
|
| 20 |
+
pyarrow==26.0.0
|
| 21 |
+
scipy==1.17.1
|
| 22 |
+
setuptools==84.2.1
|
| 23 |
+
sympy==1.14.0
|
| 24 |
+
torch==2.15.0
|
| 25 |
+
tqdm==4.70.1
|
| 26 |
+
trimesh==5.2.0
|
| 27 |
+
truststore==0.10.5
|
| 28 |
+
typing_extensions==4.16.0
|
evaluation.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_v2": {
|
| 3 |
+
"sha256": "c051b3f93d874f7c638588c9d0381754d3f954b1cd271121a033b2478d36bb1a",
|
| 4 |
+
"metrics": {
|
| 5 |
+
"occupancy": 0.5241638378953779,
|
| 6 |
+
"position": 0.0944967952302911,
|
| 7 |
+
"colour": 0.03564881652105273,
|
| 8 |
+
"scale": 0.0022415842836557938,
|
| 9 |
+
"rotation": 1.5440619935813406e-05,
|
| 10 |
+
"opacity": 0.00036697591761591643,
|
| 11 |
+
"geometry": 0.0,
|
| 12 |
+
"loss": 0.6905509463765405,
|
| 13 |
+
"occupancy_iou": 0.2868661397414935
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"candidate_v3": {
|
| 17 |
+
"sha256": "ec7c37e8ff293877149822150a5e08e8dde3e37fe0e6cfc1dc71e05adecd7790",
|
| 18 |
+
"metrics": {
|
| 19 |
+
"occupancy": 0.38844528866859224,
|
| 20 |
+
"position": 0.09291809090933242,
|
| 21 |
+
"colour": 0.03145392079041612,
|
| 22 |
+
"scale": 0.0012586073008405238,
|
| 23 |
+
"rotation": 2.456774355854217e-06,
|
| 24 |
+
"opacity": 8.929977638831883e-05,
|
| 25 |
+
"geometry": 0.0,
|
| 26 |
+
"loss": 0.5444866280954379,
|
| 27 |
+
"occupancy_iou": 0.33986033104940666
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
"gates": {
|
| 31 |
+
"training_complete": true,
|
| 32 |
+
"validation_loss_improved": true,
|
| 33 |
+
"test_loss_improved_at_least_half_percent": true,
|
| 34 |
+
"test_occupancy_iou_not_worse": true,
|
| 35 |
+
"weights_changed": true
|
| 36 |
+
},
|
| 37 |
+
"passed": true,
|
| 38 |
+
"test_manifest_sha256": "c8f927c42ba2ccecd960536de8c7c7431c8fe674401e1fbe7d7613d18cab860d",
|
| 39 |
+
"measurement": "mean per-view proxy loss and occupancy IoU, threshold 0.5, batch size 1",
|
| 40 |
+
"limitation": "Not evidence of natural-photo, mesh, texture or photorealistic rendering quality."
|
| 41 |
+
}
|
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.
|
licenses/LICENSE-ABO-CC-BY-4.0.txt
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Creative Commons Attribution 4.0 International Public License
|
| 2 |
+
|
| 3 |
+
By exercising the Licensed Rights (defined below), You accept and agree
|
| 4 |
+
to be bound by the terms and conditions of this Creative Commons
|
| 5 |
+
Attribution 4.0 International Public License ("Public License"). To the
|
| 6 |
+
extent this Public License may be interpreted as a contract, You are
|
| 7 |
+
granted the Licensed Rights in consideration of Your acceptance of
|
| 8 |
+
these terms and conditions, and the Licensor grants You such rights in
|
| 9 |
+
consideration of benefits the Licensor receives from making the
|
| 10 |
+
Licensed Material available under these terms and conditions.
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
Section 1 -- Definitions.
|
| 14 |
+
|
| 15 |
+
a. Adapted Material means material subject to Copyright and Similar
|
| 16 |
+
Rights that is derived from or based upon the Licensed Material
|
| 17 |
+
and in which the Licensed Material is translated, altered,
|
| 18 |
+
arranged, transformed, or otherwise modified in a manner requiring
|
| 19 |
+
permission under the Copyright and Similar Rights held by the
|
| 20 |
+
Licensor. For purposes of this Public License, where the Licensed
|
| 21 |
+
Material is a musical work, performance, or sound recording,
|
| 22 |
+
Adapted Material is always produced where the Licensed Material is
|
| 23 |
+
synched in timed relation with a moving image.
|
| 24 |
+
|
| 25 |
+
b. Adapter's License means the license You apply to Your Copyright
|
| 26 |
+
and Similar Rights in Your contributions to Adapted Material in
|
| 27 |
+
accordance with the terms and conditions of this Public License.
|
| 28 |
+
|
| 29 |
+
c. Copyright and Similar Rights means copyright and/or similar rights
|
| 30 |
+
closely related to copyright including, without limitation,
|
| 31 |
+
performance, broadcast, sound recording, and Sui Generis Database
|
| 32 |
+
Rights, without regard to how the rights are labeled or
|
| 33 |
+
categorized. For purposes of this Public License, the rights
|
| 34 |
+
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
| 35 |
+
Rights.
|
| 36 |
+
|
| 37 |
+
d. Effective Technological Measures means those measures that, in the
|
| 38 |
+
absence of proper authority, may not be circumvented under laws
|
| 39 |
+
fulfilling obligations under Article 11 of the WIPO Copyright
|
| 40 |
+
Treaty adopted on December 20, 1996, and/or similar international
|
| 41 |
+
agreements.
|
| 42 |
+
|
| 43 |
+
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
| 44 |
+
any other exception or limitation to Copyright and Similar Rights
|
| 45 |
+
that applies to Your use of the Licensed Material.
|
| 46 |
+
|
| 47 |
+
f. Licensed Material means the artistic or literary work, database,
|
| 48 |
+
or other material to which the Licensor applied this Public
|
| 49 |
+
License.
|
| 50 |
+
|
| 51 |
+
g. Licensed Rights means the rights granted to You subject to the
|
| 52 |
+
terms and conditions of this Public License, which are limited to
|
| 53 |
+
all Copyright and Similar Rights that apply to Your use of the
|
| 54 |
+
Licensed Material and that the Licensor has authority to license.
|
| 55 |
+
|
| 56 |
+
h. Licensor means the individual(s) or entity(ies) granting rights
|
| 57 |
+
under this Public License.
|
| 58 |
+
|
| 59 |
+
i. Share means to provide material to the public by any means or
|
| 60 |
+
process that requires permission under the Licensed Rights, such
|
| 61 |
+
as reproduction, public display, public performance, distribution,
|
| 62 |
+
dissemination, communication, or importation, and to make material
|
| 63 |
+
available to the public including in ways that members of the
|
| 64 |
+
public may access the material from a place and at a time
|
| 65 |
+
individually chosen by them.
|
| 66 |
+
|
| 67 |
+
j. Sui Generis Database Rights means rights other than copyright
|
| 68 |
+
resulting from Directive 96/9/EC of the European Parliament and of
|
| 69 |
+
the Council of 11 March 1996 on the legal protection of databases,
|
| 70 |
+
as amended and/or succeeded, as well as other essentially
|
| 71 |
+
equivalent rights anywhere in the world.
|
| 72 |
+
|
| 73 |
+
k. You means the individual or entity exercising the Licensed Rights
|
| 74 |
+
under this Public License. Your has a corresponding meaning.
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
Section 2 -- Scope.
|
| 78 |
+
|
| 79 |
+
a. License grant.
|
| 80 |
+
|
| 81 |
+
1. Subject to the terms and conditions of this Public License,
|
| 82 |
+
the Licensor hereby grants You a worldwide, royalty-free,
|
| 83 |
+
non-sublicensable, non-exclusive, irrevocable license to
|
| 84 |
+
exercise the Licensed Rights in the Licensed Material to:
|
| 85 |
+
|
| 86 |
+
a. reproduce and Share the Licensed Material, in whole or
|
| 87 |
+
in part; and
|
| 88 |
+
|
| 89 |
+
b. produce, reproduce, and Share Adapted Material.
|
| 90 |
+
|
| 91 |
+
2. Exceptions and Limitations. For the avoidance of doubt, where
|
| 92 |
+
Exceptions and Limitations apply to Your use, this Public
|
| 93 |
+
License does not apply, and You do not need to comply with
|
| 94 |
+
its terms and conditions.
|
| 95 |
+
|
| 96 |
+
3. Term. The term of this Public License is specified in Section
|
| 97 |
+
6(a).
|
| 98 |
+
|
| 99 |
+
4. Media and formats; technical modifications allowed. The
|
| 100 |
+
Licensor authorizes You to exercise the Licensed Rights in
|
| 101 |
+
all media and formats whether now known or hereafter created,
|
| 102 |
+
and to make technical modifications necessary to do so. The
|
| 103 |
+
Licensor waives and/or agrees not to assert any right or
|
| 104 |
+
authority to forbid You from making technical modifications
|
| 105 |
+
necessary to exercise the Licensed Rights, including
|
| 106 |
+
technical modifications necessary to circumvent Effective
|
| 107 |
+
Technological Measures. For purposes of this Public License,
|
| 108 |
+
simply making modifications authorized by this Section 2(a)
|
| 109 |
+
(4) never produces Adapted Material.
|
| 110 |
+
|
| 111 |
+
5. Downstream recipients.
|
| 112 |
+
|
| 113 |
+
a. Offer from the Licensor -- Licensed Material. Every
|
| 114 |
+
recipient of the Licensed Material automatically
|
| 115 |
+
receives an offer from the Licensor to exercise the
|
| 116 |
+
Licensed Rights under the terms and conditions of this
|
| 117 |
+
Public License.
|
| 118 |
+
|
| 119 |
+
b. No downstream restrictions. You may not offer or impose
|
| 120 |
+
any additional or different terms or conditions on, or
|
| 121 |
+
apply any Effective Technological Measures to, the
|
| 122 |
+
Licensed Material if doing so restricts exercise of the
|
| 123 |
+
Licensed Rights by any recipient of the Licensed
|
| 124 |
+
Material.
|
| 125 |
+
|
| 126 |
+
6. No endorsement. Nothing in this Public License constitutes or
|
| 127 |
+
may be construed as permission to assert or imply that You
|
| 128 |
+
are, or that Your use of the Licensed Material is, connected
|
| 129 |
+
with, or sponsored, endorsed, or granted official status by,
|
| 130 |
+
the Licensor or others designated to receive attribution as
|
| 131 |
+
provided in Section 3(a)(1)(A)(i).
|
| 132 |
+
|
| 133 |
+
b. Other rights.
|
| 134 |
+
|
| 135 |
+
1. Moral rights, such as the right of integrity, are not
|
| 136 |
+
licensed under this Public License, nor are publicity,
|
| 137 |
+
privacy, and/or other similar personality rights; however, to
|
| 138 |
+
the extent possible, the Licensor waives and/or agrees not to
|
| 139 |
+
assert any such rights held by the Licensor to the limited
|
| 140 |
+
extent necessary to allow You to exercise the Licensed
|
| 141 |
+
Rights, but not otherwise.
|
| 142 |
+
|
| 143 |
+
2. Patent and trademark rights are not licensed under this
|
| 144 |
+
Public License.
|
| 145 |
+
|
| 146 |
+
3. To the extent possible, the Licensor waives any right to
|
| 147 |
+
collect royalties from You for the exercise of the Licensed
|
| 148 |
+
Rights, whether directly or through a collecting society
|
| 149 |
+
under any voluntary or waivable statutory or compulsory
|
| 150 |
+
licensing scheme. In all other cases the Licensor expressly
|
| 151 |
+
reserves any right to collect such royalties.
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
Section 3 -- License Conditions.
|
| 155 |
+
|
| 156 |
+
Your exercise of the Licensed Rights is expressly made subject to the
|
| 157 |
+
following conditions.
|
| 158 |
+
|
| 159 |
+
a. Attribution.
|
| 160 |
+
|
| 161 |
+
1. If You Share the Licensed Material (including in modified
|
| 162 |
+
form), You must:
|
| 163 |
+
|
| 164 |
+
a. retain the following if it is supplied by the Licensor
|
| 165 |
+
with the Licensed Material:
|
| 166 |
+
|
| 167 |
+
i. identification of the creator(s) of the Licensed
|
| 168 |
+
Material and any others designated to receive
|
| 169 |
+
attribution, in any reasonable manner requested by
|
| 170 |
+
the Licensor (including by pseudonym if
|
| 171 |
+
designated);
|
| 172 |
+
|
| 173 |
+
ii. a copyright notice;
|
| 174 |
+
|
| 175 |
+
iii. a notice that refers to this Public License;
|
| 176 |
+
|
| 177 |
+
iv. a notice that refers to the disclaimer of
|
| 178 |
+
warranties;
|
| 179 |
+
|
| 180 |
+
v. a URI or hyperlink to the Licensed Material to the
|
| 181 |
+
extent reasonably practicable;
|
| 182 |
+
|
| 183 |
+
b. indicate if You modified the Licensed Material and
|
| 184 |
+
retain an indication of any previous modifications; and
|
| 185 |
+
|
| 186 |
+
c. indicate the Licensed Material is licensed under this
|
| 187 |
+
Public License, and include the text of, or the URI or
|
| 188 |
+
hyperlink to, this Public License.
|
| 189 |
+
|
| 190 |
+
2. You may satisfy the conditions in Section 3(a)(1) in any
|
| 191 |
+
reasonable manner based on the medium, means, and context in
|
| 192 |
+
which You Share the Licensed Material. For example, it may be
|
| 193 |
+
reasonable to satisfy the conditions by providing a URI or
|
| 194 |
+
hyperlink to a resource that includes the required
|
| 195 |
+
information.
|
| 196 |
+
|
| 197 |
+
3. If requested by the Licensor, You must remove any of the
|
| 198 |
+
information required by Section 3(a)(1)(A) to the extent
|
| 199 |
+
reasonably practicable.
|
| 200 |
+
|
| 201 |
+
4. If You Share Adapted Material You produce, the Adapter's
|
| 202 |
+
License You apply must not prevent recipients of the Adapted
|
| 203 |
+
Material from complying with this Public License.
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
Section 4 -- Sui Generis Database Rights.
|
| 207 |
+
|
| 208 |
+
Where the Licensed Rights include Sui Generis Database Rights that
|
| 209 |
+
apply to Your use of the Licensed Material:
|
| 210 |
+
|
| 211 |
+
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
| 212 |
+
to extract, reuse, reproduce, and Share all or a substantial
|
| 213 |
+
portion of the contents of the database;
|
| 214 |
+
|
| 215 |
+
b. if You include all or a substantial portion of the database
|
| 216 |
+
contents in a database in which You have Sui Generis Database
|
| 217 |
+
Rights, then the database in which You have Sui Generis Database
|
| 218 |
+
Rights (but not its individual contents) is Adapted Material; and
|
| 219 |
+
|
| 220 |
+
c. You must comply with the conditions in Section 3(a) if You Share
|
| 221 |
+
all or a substantial portion of the contents of the database.
|
| 222 |
+
|
| 223 |
+
For the avoidance of doubt, this Section 4 supplements and does not
|
| 224 |
+
replace Your obligations under this Public License where the Licensed
|
| 225 |
+
Rights include other Copyright and Similar Rights.
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
| 229 |
+
|
| 230 |
+
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
| 231 |
+
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
| 232 |
+
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
| 233 |
+
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
| 234 |
+
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
| 235 |
+
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
| 236 |
+
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
| 237 |
+
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
| 238 |
+
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
| 239 |
+
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
| 240 |
+
|
| 241 |
+
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
| 242 |
+
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
| 243 |
+
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
| 244 |
+
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
| 245 |
+
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
| 246 |
+
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
| 247 |
+
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
| 248 |
+
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
| 249 |
+
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
| 250 |
+
|
| 251 |
+
c. The disclaimer of warranties and limitation of liability provided
|
| 252 |
+
above shall be interpreted in a manner that, to the extent
|
| 253 |
+
possible, most closely approximates an absolute disclaimer and
|
| 254 |
+
waiver of all liability.
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
Section 6 -- Term and Termination.
|
| 258 |
+
|
| 259 |
+
a. This Public License applies for the term of the Copyright and
|
| 260 |
+
Similar Rights licensed here. However, if You fail to comply with
|
| 261 |
+
this Public License, then Your rights under this Public License
|
| 262 |
+
terminate automatically.
|
| 263 |
+
|
| 264 |
+
b. Where Your right to use the Licensed Material has terminated under
|
| 265 |
+
Section 6(a), it reinstates:
|
| 266 |
+
|
| 267 |
+
1. automatically as of the date the violation is cured, provided
|
| 268 |
+
it is cured within 30 days of Your discovery of the
|
| 269 |
+
violation; or
|
| 270 |
+
|
| 271 |
+
2. upon express reinstatement by the Licensor.
|
| 272 |
+
|
| 273 |
+
For the avoidance of doubt, this Section 6(b) does not affect any
|
| 274 |
+
right the Licensor may have to seek remedies for Your violations
|
| 275 |
+
of this Public License.
|
| 276 |
+
|
| 277 |
+
c. For the avoidance of doubt, the Licensor may also offer the
|
| 278 |
+
Licensed Material under separate terms or conditions or stop
|
| 279 |
+
distributing the Licensed Material at any time; however, doing so
|
| 280 |
+
will not terminate this Public License.
|
| 281 |
+
|
| 282 |
+
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
| 283 |
+
License.
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
Section 7 -- Other Terms and Conditions.
|
| 287 |
+
|
| 288 |
+
a. The Licensor shall not be bound by any additional or different
|
| 289 |
+
terms or conditions communicated by You unless expressly agreed.
|
| 290 |
+
|
| 291 |
+
b. Any arrangements, understandings, or agreements regarding the
|
| 292 |
+
Licensed Material not stated herein are separate from and
|
| 293 |
+
independent of the terms and conditions of this Public License.
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
Section 8 -- Interpretation.
|
| 297 |
+
|
| 298 |
+
a. For the avoidance of doubt, this Public License does not, and
|
| 299 |
+
shall not be interpreted to, reduce, limit, restrict, or impose
|
| 300 |
+
conditions on any use of the Licensed Material that could lawfully
|
| 301 |
+
be made without permission under this Public License.
|
| 302 |
+
|
| 303 |
+
b. To the extent possible, if any provision of this Public License is
|
| 304 |
+
deemed unenforceable, it shall be automatically reformed to the
|
| 305 |
+
minimum extent necessary to make it enforceable. If the provision
|
| 306 |
+
cannot be reformed, it shall be severed from this Public License
|
| 307 |
+
without affecting the enforceability of the remaining terms and
|
| 308 |
+
conditions.
|
| 309 |
+
|
| 310 |
+
c. No term or condition of this Public License will be waived and no
|
| 311 |
+
failure to comply consented to unless expressly agreed to by the
|
| 312 |
+
Licensor.
|
| 313 |
+
|
| 314 |
+
d. Nothing in this Public License constitutes or may be interpreted
|
| 315 |
+
as a limitation upon, or waiver of, any privileges and immunities
|
| 316 |
+
that apply to the Licensor or You, including from the legal
|
| 317 |
+
processes of any jurisdiction or authority.
|
metrics.jsonl
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"step": 250, "validation": {"occupancy": 0.46106726838939494, "position": 0.09389111219808974, "colour": 0.04310893663230296, "scale": 0.0017047523277184133, "rotation": 7.976808186366066e-06, "opacity": 0.0001671786365754631, "geometry": 0.0, "loss": 0.6415147080846216, "occupancy_iou": 0.28041237205181313}, "best_step": 250}
|
| 2 |
+
{"step": 500, "validation": {"occupancy": 0.45162729049722355, "position": 0.09384130607305587, "colour": 0.04260444884213897, "scale": 0.001661866389471909, "rotation": 7.302595148517532e-06, "opacity": 0.00015287357286561227, "geometry": 0.0, "loss": 0.6309972834484331, "occupancy_iou": 0.2809994066583699}, "best_step": 500}
|
| 3 |
+
{"step": 750, "validation": {"occupancy": 0.4463731253455425, "position": 0.09366091319370544, "colour": 0.04174524028390785, "scale": 0.0015576746118731327, "rotation": 6.378893373750188e-06, "opacity": 0.00014788560389499106, "geometry": 0.0, "loss": 0.623828811121398, "occupancy_iou": 0.28580000422809315}, "best_step": 750}
|
| 4 |
+
{"step": 1000, "validation": {"occupancy": 0.44312713128225556, "position": 0.09379177611192753, "colour": 0.04165307760182447, "scale": 0.0015555841554874212, "rotation": 5.9876457319153386e-06, "opacity": 0.00015077445404988366, "geometry": 0.0, "loss": 0.6205319956630125, "occupancy_iou": 0.2945208903830284}, "best_step": 1000}
|
| 5 |
+
{"step": 1250, "validation": {"occupancy": 0.44061438426717947, "position": 0.09367082068889306, "colour": 0.04216459069863342, "scale": 0.0014779921650355306, "rotation": 5.084008926746981e-06, "opacity": 0.00011434342663647087, "geometry": 0.0, "loss": 0.6188770349340877, "occupancy_iou": 0.2978490527569391}, "best_step": 1250}
|
| 6 |
+
{"step": 1500, "validation": {"occupancy": 0.4407315632735176, "position": 0.09352761307923958, "colour": 0.04121334745345273, "scale": 0.001461793350182265, "rotation": 5.134973895207005e-06, "opacity": 0.00013860988812520263, "geometry": 0.0, "loss": 0.6169711736769512, "occupancy_iou": 0.30566408061261835}, "best_step": 1500}
|
| 7 |
+
{"step": 1750, "validation": {"occupancy": 0.4404393983469612, "position": 0.09349199953949315, "colour": 0.04051890540187282, "scale": 0.0014701240166457071, "rotation": 4.675330279103517e-06, "opacity": 0.0001237910014685764, "geometry": 0.0, "loss": 0.6152404779675363, "occupancy_iou": 0.2812621619595193}, "best_step": 1750}
|
| 8 |
+
{"step": 2000, "validation": {"occupancy": 0.4346818130506181, "position": 0.09341091703323798, "colour": 0.040201177800073554, "scale": 0.001307281967079877, "rotation": 4.513101891955652e-06, "opacity": 0.00011089177358361217, "geometry": 0.0, "loss": 0.6087371564802082, "occupancy_iou": 0.3118145927444272}, "best_step": 2000}
|
| 9 |
+
{"step": 2250, "validation": {"occupancy": 0.43869901795325605, "position": 0.09344819003994438, "colour": 0.03963602398473357, "scale": 0.0013264096483364754, "rotation": 4.0235361918947135e-06, "opacity": 0.00012048149227112667, "geometry": 0.0, "loss": 0.6116727790955839, "occupancy_iou": 0.327563153215866}, "best_step": 2000}
|
| 10 |
+
{"step": 2500, "validation": {"occupancy": 0.43227739353803385, "position": 0.0933333754154115, "colour": 0.04032809674817113, "scale": 0.0014659382510196347, "rotation": 3.973887600551834e-06, "opacity": 0.00011141035008285113, "geometry": 0.0, "loss": 0.6065253679608477, "occupancy_iou": 0.317919851869516}, "best_step": 2500}
|
| 11 |
+
{"step": 2750, "validation": {"occupancy": 0.4353166666181608, "position": 0.09328038397448501, "colour": 0.040686976895854945, "scale": 0.0012758097041166794, "rotation": 3.63702629057905e-06, "opacity": 0.0001189368781472266, "geometry": 0.0, "loss": 0.6102178843542078, "occupancy_iou": 0.3161811849487753}, "best_step": 2500}
|
| 12 |
+
{"step": 3000, "validation": {"occupancy": 0.4327074812147124, "position": 0.09331944621243012, "colour": 0.039487305421605653, "scale": 0.0012567756840880242, "rotation": 3.5919594875489063e-06, "opacity": 0.00010452814740869792, "geometry": 0.0, "loss": 0.6052321044878027, "occupancy_iou": 0.32888385336632014}, "best_step": 3000}
|
| 13 |
+
{"step": 3250, "validation": {"occupancy": 0.4364492620031039, "position": 0.09324339584543787, "colour": 0.04059594625760346, "scale": 0.0014138426100627798, "rotation": 3.2943687288110042e-06, "opacity": 9.927087626499412e-05, "geometry": 0.0, "loss": 0.6111255342076565, "occupancy_iou": 0.3483805300389556}, "best_step": 3000}
|
| 14 |
+
{"step": 3500, "validation": {"occupancy": 0.42581871918391906, "position": 0.09325098914319072, "colour": 0.03928116287306171, "scale": 0.0011773849444413002, "rotation": 3.056185458513812e-06, "opacity": 9.935026393535757e-05, "geometry": 0.0, "loss": 0.5978494251425239, "occupancy_iou": 0.30775405541490547}, "best_step": 3500}
|
| 15 |
+
{"step": 3750, "validation": {"occupancy": 0.432027333469569, "position": 0.09319557866145825, "colour": 0.040138308227131804, "scale": 0.0011525598650526565, "rotation": 3.166901425911733e-06, "opacity": 9.929725881935239e-05, "geometry": 0.0, "loss": 0.6057144006093343, "occupancy_iou": 0.3355185413711715}, "best_step": 3500}
|
| 16 |
+
{"step": 4000, "validation": {"occupancy": 0.42550981757712775, "position": 0.09316891669456301, "colour": 0.04052523164856033, "scale": 0.0012177044145887364, "rotation": 2.9242365325929356e-06, "opacity": 0.00010036813353445754, "geometry": 0.0, "loss": 0.5999516276107437, "occupancy_iou": 0.3211282266667862}, "best_step": 3500}
|
| 17 |
+
{"step": 4250, "validation": {"occupancy": 0.43654163040477656, "position": 0.09321942790571985, "colour": 0.03907850670414822, "scale": 0.0011486642537023015, "rotation": 2.998764346520848e-06, "opacity": 0.00011165465859537745, "geometry": 0.0, "loss": 0.6081448930433426, "occupancy_iou": 0.35518712877970315}, "best_step": 3500}
|
| 18 |
+
{"step": 4500, "validation": {"occupancy": 0.42716445825223265, "position": 0.09306304662049501, "colour": 0.03876508917791994, "scale": 0.001119821725045893, "rotation": 2.8751217178591753e-06, "opacity": 9.490183666541229e-05, "geometry": 0.0, "loss": 0.5979648541616297, "occupancy_iou": 0.33583940336502144}, "best_step": 3500}
|
| 19 |
+
{"step": 4750, "validation": {"occupancy": 0.4252900903637724, "position": 0.09302231579505164, "colour": 0.03992765121651685, "scale": 0.001186143789385531, "rotation": 2.6761208888841394e-06, "opacity": 9.641269463733399e-05, "geometry": 0.0, "loss": 0.5983830038244011, "occupancy_iou": 0.32429440710265406}, "best_step": 3500}
|
| 20 |
+
{"step": 5000, "validation": {"occupancy": 0.4202662355469904, "position": 0.09291419757251766, "colour": 0.03968908278396669, "scale": 0.0011747911177380224, "rotation": 2.4885126973019615e-06, "opacity": 9.251441614933669e-05, "geometry": 0.0, "loss": 0.5927688404061329, "occupancy_iou": 0.32473888893708075}, "best_step": 5000}
|
| 21 |
+
{"step": 5250, "validation": {"occupancy": 0.41929296279946965, "position": 0.09285500444386197, "colour": 0.03871898723538061, "scale": 0.001147763923529118, "rotation": 2.570338795158545e-06, "opacity": 8.911200696316862e-05, "geometry": 0.0, "loss": 0.5897900897195969, "occupancy_iou": 0.3356060852328765}, "best_step": 5250}
|
| 22 |
+
{"step": 5500, "validation": {"occupancy": 0.42326834313999645, "position": 0.09288628300887415, "colour": 0.039693311325156654, "scale": 0.0011034196943690967, "rotation": 2.521009106911167e-06, "opacity": 9.737238293126566e-05, "geometry": 0.0, "loss": 0.5957492148910446, "occupancy_iou": 0.34857936678775425}, "best_step": 5250}
|
| 23 |
+
{"step": 5750, "validation": {"occupancy": 0.4186605631491576, "position": 0.09286394899432686, "colour": 0.039635561564341774, "scale": 0.001126612422368275, "rotation": 2.4452578455453876e-06, "opacity": 9.208203308760899e-05, "geometry": 0.0, "loss": 0.591000622288249, "occupancy_iou": 0.3344719207941972}, "best_step": 5250}
|
| 24 |
+
{"step": 6000, "validation": {"occupancy": 0.4138930352597401, "position": 0.09282098901083415, "colour": 0.038808255775693815, "scale": 0.0010773984927934296, "rotation": 2.286030634838985e-06, "opacity": 8.458352363703299e-05, "geometry": 0.0, "loss": 0.5845230878598389, "occupancy_iou": 0.3310552115669881}, "best_step": 6000}
|
model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ec7c37e8ff293877149822150a5e08e8dde3e37fe0e6cfc1dc71e05adecd7790
|
| 3 |
+
size 1725673
|
preparation.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "codepawl/abo-glb",
|
| 3 |
+
"revision": "c7c8293fed3b7ab122ef5d7896521a187e0962d7",
|
| 4 |
+
"objects": 512,
|
| 5 |
+
"views": 2048,
|
| 6 |
+
"groups": {
|
| 7 |
+
"train": 348,
|
| 8 |
+
"validation": 87,
|
| 9 |
+
"test": 77
|
| 10 |
+
},
|
| 11 |
+
"split_views": {
|
| 12 |
+
"train": 1392,
|
| 13 |
+
"validation": 348,
|
| 14 |
+
"test": 308
|
| 15 |
+
},
|
| 16 |
+
"manifest_sha256": {
|
| 17 |
+
"train": "65af1c16cefde7e9cd411af055ccdee36c183382db18a4fd9a9d4ea82fb248ee",
|
| 18 |
+
"validation": "65ca208e5b7d59d398056c997751b12a18ce05c9361e37f4160f3b1420cbe52a",
|
| 19 |
+
"test": "c8f927c42ba2ccecd960536de8c7c7431c8fe674401e1fbe7d7613d18cab860d"
|
| 20 |
+
},
|
| 21 |
+
"grouping": "object IDs, shared Amazon item IDs, exact mesh/image/target hashes",
|
| 22 |
+
"limitations": "Near duplicates can remain. Cross-dataset overlap with ancestor training is not fully known; only recorded exact source hashes were excluded."
|
| 23 |
+
}
|
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-3"
|
| 7 |
+
version = "0.4.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"]
|
recipe/decode.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NodeIO } from '@gltf-transform/core';
|
| 2 |
+
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
| 3 |
+
import { dequantize } from '@gltf-transform/functions';
|
| 4 |
+
import { MeshoptDecoder } from 'meshoptimizer';
|
| 5 |
+
await MeshoptDecoder.ready;
|
| 6 |
+
const io = new NodeIO().registerExtensions(ALL_EXTENSIONS)
|
| 7 |
+
.registerDependencies({ 'meshopt.decoder': MeshoptDecoder });
|
| 8 |
+
const doc = await io.read(process.argv[2]);
|
| 9 |
+
await doc.transform(dequantize());
|
| 10 |
+
for (const ext of doc.getRoot().listExtensionsUsed()) {
|
| 11 |
+
if (ext.extensionName === 'EXT_meshopt_compression') ext.dispose();
|
| 12 |
+
}
|
| 13 |
+
await io.write(process.argv[3], doc);
|
recipe/package-lock.json
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "forma3d-tiny-3-mac",
|
| 3 |
+
"lockfileVersion": 3,
|
| 4 |
+
"requires": true,
|
| 5 |
+
"packages": {
|
| 6 |
+
"": {
|
| 7 |
+
"dependencies": {
|
| 8 |
+
"@gltf-transform/core": "4.5.1",
|
| 9 |
+
"@gltf-transform/extensions": "4.5.1",
|
| 10 |
+
"@gltf-transform/functions": "4.5.1",
|
| 11 |
+
"meshoptimizer": "1.3.0"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"node_modules/@emnapi/runtime": {
|
| 15 |
+
"version": "1.11.3",
|
| 16 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
| 17 |
+
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
| 18 |
+
"license": "MIT",
|
| 19 |
+
"optional": true,
|
| 20 |
+
"dependencies": {
|
| 21 |
+
"tslib": "^2.4.0"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"node_modules/@gltf-transform/core": {
|
| 25 |
+
"version": "4.5.1",
|
| 26 |
+
"resolved": "https://registry.npmjs.org/@gltf-transform/core/-/core-4.5.1.tgz",
|
| 27 |
+
"integrity": "sha512-OWgBYssKHKoXMIrMb2N20jXqWRnMP9HdtcTFQBEAJtz4P+OOxx5fozl5eAeK7nktIs5uDE/lu4NbkE5VCZaR9g==",
|
| 28 |
+
"license": "MIT",
|
| 29 |
+
"dependencies": {
|
| 30 |
+
"property-graph": "^4.1.0"
|
| 31 |
+
},
|
| 32 |
+
"funding": {
|
| 33 |
+
"url": "https://github.com/sponsors/donmccurdy"
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"node_modules/@gltf-transform/extensions": {
|
| 37 |
+
"version": "4.5.1",
|
| 38 |
+
"resolved": "https://registry.npmjs.org/@gltf-transform/extensions/-/extensions-4.5.1.tgz",
|
| 39 |
+
"integrity": "sha512-3jTe+GWRy4fj1TU790LPsO+rpO/4byUWPTpNkjy6M6h8RvrUivRf12Mptc0770K4ZczibpJDNHXZFkFsFMRDLA==",
|
| 40 |
+
"license": "MIT",
|
| 41 |
+
"dependencies": {
|
| 42 |
+
"@gltf-transform/core": "^4.5.1",
|
| 43 |
+
"ktx-parse": "^1.1.0"
|
| 44 |
+
},
|
| 45 |
+
"funding": {
|
| 46 |
+
"url": "https://github.com/sponsors/donmccurdy"
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"node_modules/@gltf-transform/functions": {
|
| 50 |
+
"version": "4.5.1",
|
| 51 |
+
"resolved": "https://registry.npmjs.org/@gltf-transform/functions/-/functions-4.5.1.tgz",
|
| 52 |
+
"integrity": "sha512-rfm4nQY2DJ+ip1e15xp7YfQwhRno3kPLcGw4EZiRk1dVwMV3cdFMcF08glYcO/+oLyYkUEs3xXMJYXa0cPgcTg==",
|
| 53 |
+
"license": "MIT",
|
| 54 |
+
"dependencies": {
|
| 55 |
+
"@gltf-transform/core": "^4.5.1",
|
| 56 |
+
"@gltf-transform/extensions": "^4.5.1",
|
| 57 |
+
"ktx-parse": "^1.1.0",
|
| 58 |
+
"ndarray": "^1.0.19",
|
| 59 |
+
"ndarray-lanczos": "^0.3.0",
|
| 60 |
+
"ndarray-pixels": "^5.2.0"
|
| 61 |
+
},
|
| 62 |
+
"funding": {
|
| 63 |
+
"url": "https://github.com/sponsors/donmccurdy"
|
| 64 |
+
}
|
| 65 |
+
},
|
| 66 |
+
"node_modules/@img/colour": {
|
| 67 |
+
"version": "1.1.0",
|
| 68 |
+
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
| 69 |
+
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
| 70 |
+
"license": "MIT",
|
| 71 |
+
"engines": {
|
| 72 |
+
"node": ">=18"
|
| 73 |
+
}
|
| 74 |
+
},
|
| 75 |
+
"node_modules/@img/sharp-darwin-arm64": {
|
| 76 |
+
"version": "0.35.5",
|
| 77 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz",
|
| 78 |
+
"integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==",
|
| 79 |
+
"cpu": [
|
| 80 |
+
"arm64"
|
| 81 |
+
],
|
| 82 |
+
"license": "Apache-2.0",
|
| 83 |
+
"optional": true,
|
| 84 |
+
"os": [
|
| 85 |
+
"darwin"
|
| 86 |
+
],
|
| 87 |
+
"engines": {
|
| 88 |
+
"node": ">=20.9.0"
|
| 89 |
+
},
|
| 90 |
+
"funding": {
|
| 91 |
+
"url": "https://opencollective.com/libvips"
|
| 92 |
+
},
|
| 93 |
+
"optionalDependencies": {
|
| 94 |
+
"@img/sharp-libvips-darwin-arm64": "1.3.4"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/@img/sharp-darwin-x64": {
|
| 98 |
+
"version": "0.35.5",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz",
|
| 100 |
+
"integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==",
|
| 101 |
+
"cpu": [
|
| 102 |
+
"x64"
|
| 103 |
+
],
|
| 104 |
+
"license": "Apache-2.0",
|
| 105 |
+
"optional": true,
|
| 106 |
+
"os": [
|
| 107 |
+
"darwin"
|
| 108 |
+
],
|
| 109 |
+
"engines": {
|
| 110 |
+
"node": ">=20.9.0"
|
| 111 |
+
},
|
| 112 |
+
"funding": {
|
| 113 |
+
"url": "https://opencollective.com/libvips"
|
| 114 |
+
},
|
| 115 |
+
"optionalDependencies": {
|
| 116 |
+
"@img/sharp-libvips-darwin-x64": "1.3.4"
|
| 117 |
+
}
|
| 118 |
+
},
|
| 119 |
+
"node_modules/@img/sharp-freebsd-wasm32": {
|
| 120 |
+
"version": "0.35.5",
|
| 121 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz",
|
| 122 |
+
"integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==",
|
| 123 |
+
"license": "Apache-2.0",
|
| 124 |
+
"optional": true,
|
| 125 |
+
"os": [
|
| 126 |
+
"freebsd"
|
| 127 |
+
],
|
| 128 |
+
"dependencies": {
|
| 129 |
+
"@img/sharp-wasm32": "0.35.5"
|
| 130 |
+
},
|
| 131 |
+
"engines": {
|
| 132 |
+
"node": ">=20.9.0"
|
| 133 |
+
},
|
| 134 |
+
"funding": {
|
| 135 |
+
"url": "https://opencollective.com/libvips"
|
| 136 |
+
}
|
| 137 |
+
},
|
| 138 |
+
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
| 139 |
+
"version": "1.3.4",
|
| 140 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz",
|
| 141 |
+
"integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==",
|
| 142 |
+
"cpu": [
|
| 143 |
+
"arm64"
|
| 144 |
+
],
|
| 145 |
+
"license": "LGPL-3.0-or-later",
|
| 146 |
+
"optional": true,
|
| 147 |
+
"os": [
|
| 148 |
+
"darwin"
|
| 149 |
+
],
|
| 150 |
+
"funding": {
|
| 151 |
+
"url": "https://opencollective.com/libvips"
|
| 152 |
+
}
|
| 153 |
+
},
|
| 154 |
+
"node_modules/@img/sharp-libvips-darwin-x64": {
|
| 155 |
+
"version": "1.3.4",
|
| 156 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz",
|
| 157 |
+
"integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==",
|
| 158 |
+
"cpu": [
|
| 159 |
+
"x64"
|
| 160 |
+
],
|
| 161 |
+
"license": "LGPL-3.0-or-later",
|
| 162 |
+
"optional": true,
|
| 163 |
+
"os": [
|
| 164 |
+
"darwin"
|
| 165 |
+
],
|
| 166 |
+
"funding": {
|
| 167 |
+
"url": "https://opencollective.com/libvips"
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
"node_modules/@img/sharp-libvips-linux-arm": {
|
| 171 |
+
"version": "1.3.4",
|
| 172 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz",
|
| 173 |
+
"integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==",
|
| 174 |
+
"cpu": [
|
| 175 |
+
"arm"
|
| 176 |
+
],
|
| 177 |
+
"libc": [
|
| 178 |
+
"glibc"
|
| 179 |
+
],
|
| 180 |
+
"license": "LGPL-3.0-or-later",
|
| 181 |
+
"optional": true,
|
| 182 |
+
"os": [
|
| 183 |
+
"linux"
|
| 184 |
+
],
|
| 185 |
+
"funding": {
|
| 186 |
+
"url": "https://opencollective.com/libvips"
|
| 187 |
+
}
|
| 188 |
+
},
|
| 189 |
+
"node_modules/@img/sharp-libvips-linux-arm64": {
|
| 190 |
+
"version": "1.3.4",
|
| 191 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz",
|
| 192 |
+
"integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==",
|
| 193 |
+
"cpu": [
|
| 194 |
+
"arm64"
|
| 195 |
+
],
|
| 196 |
+
"libc": [
|
| 197 |
+
"glibc"
|
| 198 |
+
],
|
| 199 |
+
"license": "LGPL-3.0-or-later",
|
| 200 |
+
"optional": true,
|
| 201 |
+
"os": [
|
| 202 |
+
"linux"
|
| 203 |
+
],
|
| 204 |
+
"funding": {
|
| 205 |
+
"url": "https://opencollective.com/libvips"
|
| 206 |
+
}
|
| 207 |
+
},
|
| 208 |
+
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
| 209 |
+
"version": "1.3.4",
|
| 210 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz",
|
| 211 |
+
"integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==",
|
| 212 |
+
"cpu": [
|
| 213 |
+
"ppc64"
|
| 214 |
+
],
|
| 215 |
+
"libc": [
|
| 216 |
+
"glibc"
|
| 217 |
+
],
|
| 218 |
+
"license": "LGPL-3.0-or-later",
|
| 219 |
+
"optional": true,
|
| 220 |
+
"os": [
|
| 221 |
+
"linux"
|
| 222 |
+
],
|
| 223 |
+
"funding": {
|
| 224 |
+
"url": "https://opencollective.com/libvips"
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
| 228 |
+
"version": "1.3.4",
|
| 229 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz",
|
| 230 |
+
"integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==",
|
| 231 |
+
"cpu": [
|
| 232 |
+
"riscv64"
|
| 233 |
+
],
|
| 234 |
+
"libc": [
|
| 235 |
+
"glibc"
|
| 236 |
+
],
|
| 237 |
+
"license": "LGPL-3.0-or-later",
|
| 238 |
+
"optional": true,
|
| 239 |
+
"os": [
|
| 240 |
+
"linux"
|
| 241 |
+
],
|
| 242 |
+
"funding": {
|
| 243 |
+
"url": "https://opencollective.com/libvips"
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
"node_modules/@img/sharp-libvips-linux-s390x": {
|
| 247 |
+
"version": "1.3.4",
|
| 248 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz",
|
| 249 |
+
"integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==",
|
| 250 |
+
"cpu": [
|
| 251 |
+
"s390x"
|
| 252 |
+
],
|
| 253 |
+
"libc": [
|
| 254 |
+
"glibc"
|
| 255 |
+
],
|
| 256 |
+
"license": "LGPL-3.0-or-later",
|
| 257 |
+
"optional": true,
|
| 258 |
+
"os": [
|
| 259 |
+
"linux"
|
| 260 |
+
],
|
| 261 |
+
"funding": {
|
| 262 |
+
"url": "https://opencollective.com/libvips"
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"node_modules/@img/sharp-libvips-linux-x64": {
|
| 266 |
+
"version": "1.3.4",
|
| 267 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz",
|
| 268 |
+
"integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==",
|
| 269 |
+
"cpu": [
|
| 270 |
+
"x64"
|
| 271 |
+
],
|
| 272 |
+
"libc": [
|
| 273 |
+
"glibc"
|
| 274 |
+
],
|
| 275 |
+
"license": "LGPL-3.0-or-later",
|
| 276 |
+
"optional": true,
|
| 277 |
+
"os": [
|
| 278 |
+
"linux"
|
| 279 |
+
],
|
| 280 |
+
"funding": {
|
| 281 |
+
"url": "https://opencollective.com/libvips"
|
| 282 |
+
}
|
| 283 |
+
},
|
| 284 |
+
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
| 285 |
+
"version": "1.3.4",
|
| 286 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz",
|
| 287 |
+
"integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==",
|
| 288 |
+
"cpu": [
|
| 289 |
+
"arm64"
|
| 290 |
+
],
|
| 291 |
+
"libc": [
|
| 292 |
+
"musl"
|
| 293 |
+
],
|
| 294 |
+
"license": "LGPL-3.0-or-later",
|
| 295 |
+
"optional": true,
|
| 296 |
+
"os": [
|
| 297 |
+
"linux"
|
| 298 |
+
],
|
| 299 |
+
"funding": {
|
| 300 |
+
"url": "https://opencollective.com/libvips"
|
| 301 |
+
}
|
| 302 |
+
},
|
| 303 |
+
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
| 304 |
+
"version": "1.3.4",
|
| 305 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz",
|
| 306 |
+
"integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==",
|
| 307 |
+
"cpu": [
|
| 308 |
+
"x64"
|
| 309 |
+
],
|
| 310 |
+
"libc": [
|
| 311 |
+
"musl"
|
| 312 |
+
],
|
| 313 |
+
"license": "LGPL-3.0-or-later",
|
| 314 |
+
"optional": true,
|
| 315 |
+
"os": [
|
| 316 |
+
"linux"
|
| 317 |
+
],
|
| 318 |
+
"funding": {
|
| 319 |
+
"url": "https://opencollective.com/libvips"
|
| 320 |
+
}
|
| 321 |
+
},
|
| 322 |
+
"node_modules/@img/sharp-linux-arm": {
|
| 323 |
+
"version": "0.35.5",
|
| 324 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz",
|
| 325 |
+
"integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==",
|
| 326 |
+
"cpu": [
|
| 327 |
+
"arm"
|
| 328 |
+
],
|
| 329 |
+
"libc": [
|
| 330 |
+
"glibc"
|
| 331 |
+
],
|
| 332 |
+
"license": "Apache-2.0",
|
| 333 |
+
"optional": true,
|
| 334 |
+
"os": [
|
| 335 |
+
"linux"
|
| 336 |
+
],
|
| 337 |
+
"engines": {
|
| 338 |
+
"node": ">=20.9.0"
|
| 339 |
+
},
|
| 340 |
+
"funding": {
|
| 341 |
+
"url": "https://opencollective.com/libvips"
|
| 342 |
+
},
|
| 343 |
+
"optionalDependencies": {
|
| 344 |
+
"@img/sharp-libvips-linux-arm": "1.3.4"
|
| 345 |
+
}
|
| 346 |
+
},
|
| 347 |
+
"node_modules/@img/sharp-linux-arm64": {
|
| 348 |
+
"version": "0.35.5",
|
| 349 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz",
|
| 350 |
+
"integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==",
|
| 351 |
+
"cpu": [
|
| 352 |
+
"arm64"
|
| 353 |
+
],
|
| 354 |
+
"libc": [
|
| 355 |
+
"glibc"
|
| 356 |
+
],
|
| 357 |
+
"license": "Apache-2.0",
|
| 358 |
+
"optional": true,
|
| 359 |
+
"os": [
|
| 360 |
+
"linux"
|
| 361 |
+
],
|
| 362 |
+
"engines": {
|
| 363 |
+
"node": ">=20.9.0"
|
| 364 |
+
},
|
| 365 |
+
"funding": {
|
| 366 |
+
"url": "https://opencollective.com/libvips"
|
| 367 |
+
},
|
| 368 |
+
"optionalDependencies": {
|
| 369 |
+
"@img/sharp-libvips-linux-arm64": "1.3.4"
|
| 370 |
+
}
|
| 371 |
+
},
|
| 372 |
+
"node_modules/@img/sharp-linux-ppc64": {
|
| 373 |
+
"version": "0.35.5",
|
| 374 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz",
|
| 375 |
+
"integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==",
|
| 376 |
+
"cpu": [
|
| 377 |
+
"ppc64"
|
| 378 |
+
],
|
| 379 |
+
"libc": [
|
| 380 |
+
"glibc"
|
| 381 |
+
],
|
| 382 |
+
"license": "Apache-2.0",
|
| 383 |
+
"optional": true,
|
| 384 |
+
"os": [
|
| 385 |
+
"linux"
|
| 386 |
+
],
|
| 387 |
+
"engines": {
|
| 388 |
+
"node": ">=20.9.0"
|
| 389 |
+
},
|
| 390 |
+
"funding": {
|
| 391 |
+
"url": "https://opencollective.com/libvips"
|
| 392 |
+
},
|
| 393 |
+
"optionalDependencies": {
|
| 394 |
+
"@img/sharp-libvips-linux-ppc64": "1.3.4"
|
| 395 |
+
}
|
| 396 |
+
},
|
| 397 |
+
"node_modules/@img/sharp-linux-riscv64": {
|
| 398 |
+
"version": "0.35.5",
|
| 399 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz",
|
| 400 |
+
"integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==",
|
| 401 |
+
"cpu": [
|
| 402 |
+
"riscv64"
|
| 403 |
+
],
|
| 404 |
+
"libc": [
|
| 405 |
+
"glibc"
|
| 406 |
+
],
|
| 407 |
+
"license": "Apache-2.0",
|
| 408 |
+
"optional": true,
|
| 409 |
+
"os": [
|
| 410 |
+
"linux"
|
| 411 |
+
],
|
| 412 |
+
"engines": {
|
| 413 |
+
"node": ">=20.9.0"
|
| 414 |
+
},
|
| 415 |
+
"funding": {
|
| 416 |
+
"url": "https://opencollective.com/libvips"
|
| 417 |
+
},
|
| 418 |
+
"optionalDependencies": {
|
| 419 |
+
"@img/sharp-libvips-linux-riscv64": "1.3.4"
|
| 420 |
+
}
|
| 421 |
+
},
|
| 422 |
+
"node_modules/@img/sharp-linux-s390x": {
|
| 423 |
+
"version": "0.35.5",
|
| 424 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz",
|
| 425 |
+
"integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==",
|
| 426 |
+
"cpu": [
|
| 427 |
+
"s390x"
|
| 428 |
+
],
|
| 429 |
+
"libc": [
|
| 430 |
+
"glibc"
|
| 431 |
+
],
|
| 432 |
+
"license": "Apache-2.0",
|
| 433 |
+
"optional": true,
|
| 434 |
+
"os": [
|
| 435 |
+
"linux"
|
| 436 |
+
],
|
| 437 |
+
"engines": {
|
| 438 |
+
"node": ">=20.9.0"
|
| 439 |
+
},
|
| 440 |
+
"funding": {
|
| 441 |
+
"url": "https://opencollective.com/libvips"
|
| 442 |
+
},
|
| 443 |
+
"optionalDependencies": {
|
| 444 |
+
"@img/sharp-libvips-linux-s390x": "1.3.4"
|
| 445 |
+
}
|
| 446 |
+
},
|
| 447 |
+
"node_modules/@img/sharp-linux-x64": {
|
| 448 |
+
"version": "0.35.5",
|
| 449 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz",
|
| 450 |
+
"integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==",
|
| 451 |
+
"cpu": [
|
| 452 |
+
"x64"
|
| 453 |
+
],
|
| 454 |
+
"libc": [
|
| 455 |
+
"glibc"
|
| 456 |
+
],
|
| 457 |
+
"license": "Apache-2.0",
|
| 458 |
+
"optional": true,
|
| 459 |
+
"os": [
|
| 460 |
+
"linux"
|
| 461 |
+
],
|
| 462 |
+
"engines": {
|
| 463 |
+
"node": ">=20.9.0"
|
| 464 |
+
},
|
| 465 |
+
"funding": {
|
| 466 |
+
"url": "https://opencollective.com/libvips"
|
| 467 |
+
},
|
| 468 |
+
"optionalDependencies": {
|
| 469 |
+
"@img/sharp-libvips-linux-x64": "1.3.4"
|
| 470 |
+
}
|
| 471 |
+
},
|
| 472 |
+
"node_modules/@img/sharp-linuxmusl-arm64": {
|
| 473 |
+
"version": "0.35.5",
|
| 474 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz",
|
| 475 |
+
"integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==",
|
| 476 |
+
"cpu": [
|
| 477 |
+
"arm64"
|
| 478 |
+
],
|
| 479 |
+
"libc": [
|
| 480 |
+
"musl"
|
| 481 |
+
],
|
| 482 |
+
"license": "Apache-2.0",
|
| 483 |
+
"optional": true,
|
| 484 |
+
"os": [
|
| 485 |
+
"linux"
|
| 486 |
+
],
|
| 487 |
+
"engines": {
|
| 488 |
+
"node": ">=20.9.0"
|
| 489 |
+
},
|
| 490 |
+
"funding": {
|
| 491 |
+
"url": "https://opencollective.com/libvips"
|
| 492 |
+
},
|
| 493 |
+
"optionalDependencies": {
|
| 494 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4"
|
| 495 |
+
}
|
| 496 |
+
},
|
| 497 |
+
"node_modules/@img/sharp-linuxmusl-x64": {
|
| 498 |
+
"version": "0.35.5",
|
| 499 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz",
|
| 500 |
+
"integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==",
|
| 501 |
+
"cpu": [
|
| 502 |
+
"x64"
|
| 503 |
+
],
|
| 504 |
+
"libc": [
|
| 505 |
+
"musl"
|
| 506 |
+
],
|
| 507 |
+
"license": "Apache-2.0",
|
| 508 |
+
"optional": true,
|
| 509 |
+
"os": [
|
| 510 |
+
"linux"
|
| 511 |
+
],
|
| 512 |
+
"engines": {
|
| 513 |
+
"node": ">=20.9.0"
|
| 514 |
+
},
|
| 515 |
+
"funding": {
|
| 516 |
+
"url": "https://opencollective.com/libvips"
|
| 517 |
+
},
|
| 518 |
+
"optionalDependencies": {
|
| 519 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
|
| 520 |
+
}
|
| 521 |
+
},
|
| 522 |
+
"node_modules/@img/sharp-wasm32": {
|
| 523 |
+
"version": "0.35.5",
|
| 524 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz",
|
| 525 |
+
"integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==",
|
| 526 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
| 527 |
+
"optional": true,
|
| 528 |
+
"dependencies": {
|
| 529 |
+
"@emnapi/runtime": "^1.11.3"
|
| 530 |
+
},
|
| 531 |
+
"engines": {
|
| 532 |
+
"node": ">=20.9.0"
|
| 533 |
+
},
|
| 534 |
+
"funding": {
|
| 535 |
+
"url": "https://opencollective.com/libvips"
|
| 536 |
+
}
|
| 537 |
+
},
|
| 538 |
+
"node_modules/@img/sharp-webcontainers-wasm32": {
|
| 539 |
+
"version": "0.35.5",
|
| 540 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz",
|
| 541 |
+
"integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==",
|
| 542 |
+
"cpu": [
|
| 543 |
+
"wasm32"
|
| 544 |
+
],
|
| 545 |
+
"license": "Apache-2.0",
|
| 546 |
+
"optional": true,
|
| 547 |
+
"dependencies": {
|
| 548 |
+
"@img/sharp-wasm32": "0.35.5"
|
| 549 |
+
},
|
| 550 |
+
"engines": {
|
| 551 |
+
"node": ">=20.9.0"
|
| 552 |
+
},
|
| 553 |
+
"funding": {
|
| 554 |
+
"url": "https://opencollective.com/libvips"
|
| 555 |
+
}
|
| 556 |
+
},
|
| 557 |
+
"node_modules/@img/sharp-win32-arm64": {
|
| 558 |
+
"version": "0.35.5",
|
| 559 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz",
|
| 560 |
+
"integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==",
|
| 561 |
+
"cpu": [
|
| 562 |
+
"arm64"
|
| 563 |
+
],
|
| 564 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 565 |
+
"optional": true,
|
| 566 |
+
"os": [
|
| 567 |
+
"win32"
|
| 568 |
+
],
|
| 569 |
+
"engines": {
|
| 570 |
+
"node": ">=20.9.0"
|
| 571 |
+
},
|
| 572 |
+
"funding": {
|
| 573 |
+
"url": "https://opencollective.com/libvips"
|
| 574 |
+
}
|
| 575 |
+
},
|
| 576 |
+
"node_modules/@img/sharp-win32-ia32": {
|
| 577 |
+
"version": "0.35.5",
|
| 578 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz",
|
| 579 |
+
"integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==",
|
| 580 |
+
"cpu": [
|
| 581 |
+
"ia32"
|
| 582 |
+
],
|
| 583 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 584 |
+
"optional": true,
|
| 585 |
+
"os": [
|
| 586 |
+
"win32"
|
| 587 |
+
],
|
| 588 |
+
"engines": {
|
| 589 |
+
"node": "^20.9.0"
|
| 590 |
+
},
|
| 591 |
+
"funding": {
|
| 592 |
+
"url": "https://opencollective.com/libvips"
|
| 593 |
+
}
|
| 594 |
+
},
|
| 595 |
+
"node_modules/@img/sharp-win32-x64": {
|
| 596 |
+
"version": "0.35.5",
|
| 597 |
+
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz",
|
| 598 |
+
"integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==",
|
| 599 |
+
"cpu": [
|
| 600 |
+
"x64"
|
| 601 |
+
],
|
| 602 |
+
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
| 603 |
+
"optional": true,
|
| 604 |
+
"os": [
|
| 605 |
+
"win32"
|
| 606 |
+
],
|
| 607 |
+
"engines": {
|
| 608 |
+
"node": ">=20.9.0"
|
| 609 |
+
},
|
| 610 |
+
"funding": {
|
| 611 |
+
"url": "https://opencollective.com/libvips"
|
| 612 |
+
}
|
| 613 |
+
},
|
| 614 |
+
"node_modules/@types/ndarray": {
|
| 615 |
+
"version": "1.1.0",
|
| 616 |
+
"resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.1.0.tgz",
|
| 617 |
+
"integrity": "sha512-go+C6vHBUuO9jH6Tj6qEZ63JEGoMSxFdmd+6QAtthGD6xumBfIz3RbNDDs2aoRVLf9naH8lk5jnWy2UHHKgYQQ==",
|
| 618 |
+
"license": "MIT"
|
| 619 |
+
},
|
| 620 |
+
"node_modules/cwise-compiler": {
|
| 621 |
+
"version": "1.1.3",
|
| 622 |
+
"resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",
|
| 623 |
+
"integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==",
|
| 624 |
+
"license": "MIT",
|
| 625 |
+
"dependencies": {
|
| 626 |
+
"uniq": "^1.0.0"
|
| 627 |
+
}
|
| 628 |
+
},
|
| 629 |
+
"node_modules/detect-libc": {
|
| 630 |
+
"version": "2.1.2",
|
| 631 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 632 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 633 |
+
"license": "Apache-2.0",
|
| 634 |
+
"engines": {
|
| 635 |
+
"node": ">=8"
|
| 636 |
+
}
|
| 637 |
+
},
|
| 638 |
+
"node_modules/iota-array": {
|
| 639 |
+
"version": "1.0.0",
|
| 640 |
+
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
|
| 641 |
+
"integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==",
|
| 642 |
+
"license": "MIT"
|
| 643 |
+
},
|
| 644 |
+
"node_modules/is-buffer": {
|
| 645 |
+
"version": "1.1.6",
|
| 646 |
+
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
| 647 |
+
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
| 648 |
+
"license": "MIT"
|
| 649 |
+
},
|
| 650 |
+
"node_modules/ktx-parse": {
|
| 651 |
+
"version": "1.1.0",
|
| 652 |
+
"resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-1.1.0.tgz",
|
| 653 |
+
"integrity": "sha512-mKp3y+FaYgR7mXWAbyyzpa/r1zDWeaunH+INJO4fou3hb45XuNSwar+7llrRyvpMWafxSIi99RNFJ05MHedaJQ==",
|
| 654 |
+
"license": "MIT"
|
| 655 |
+
},
|
| 656 |
+
"node_modules/meshoptimizer": {
|
| 657 |
+
"version": "1.3.0",
|
| 658 |
+
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.3.0.tgz",
|
| 659 |
+
"integrity": "sha512-2oZqMAcEEiCtXWUjIGtZ71oPx1B0ZxYGTqIKsXB1wzWFhleNsCFxQuUJzgCV706bRcwPWtMGf1odmQvAcqKkIw==",
|
| 660 |
+
"license": "MIT"
|
| 661 |
+
},
|
| 662 |
+
"node_modules/ndarray": {
|
| 663 |
+
"version": "1.1.1",
|
| 664 |
+
"resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.1.1.tgz",
|
| 665 |
+
"integrity": "sha512-AsE8w/M2MS+0pD3nP5qbRRtJFeJJJaJ+qXZaZr0XjVGXhy6LIj2TERTlkpdK2AI3WufoOP+pwPk3q2ilAJcZ9g==",
|
| 666 |
+
"license": "MIT",
|
| 667 |
+
"dependencies": {
|
| 668 |
+
"iota-array": "^1.0.0",
|
| 669 |
+
"is-buffer": "^1.0.2"
|
| 670 |
+
}
|
| 671 |
+
},
|
| 672 |
+
"node_modules/ndarray-lanczos": {
|
| 673 |
+
"version": "0.3.0",
|
| 674 |
+
"resolved": "https://registry.npmjs.org/ndarray-lanczos/-/ndarray-lanczos-0.3.0.tgz",
|
| 675 |
+
"integrity": "sha512-5kBmmG3Zvyj77qxIAC4QFLKuYdDIBJwCG+DukT6jQHNa1Ft74/hPH1z5mbQXeHBt8yvGPBGVrr3wEOdJPYYZYg==",
|
| 676 |
+
"license": "MIT",
|
| 677 |
+
"dependencies": {
|
| 678 |
+
"@types/ndarray": "^1.0.11",
|
| 679 |
+
"ndarray": "^1.0.19"
|
| 680 |
+
}
|
| 681 |
+
},
|
| 682 |
+
"node_modules/ndarray-ops": {
|
| 683 |
+
"version": "1.2.2",
|
| 684 |
+
"resolved": "https://registry.npmjs.org/ndarray-ops/-/ndarray-ops-1.2.2.tgz",
|
| 685 |
+
"integrity": "sha512-BppWAFRjMYF7N/r6Ie51q6D4fs0iiGmeXIACKY66fLpnwIui3Wc3CXiD/30mgLbDjPpSLrsqcp3Z62+IcHZsDw==",
|
| 686 |
+
"license": "MIT",
|
| 687 |
+
"dependencies": {
|
| 688 |
+
"cwise-compiler": "^1.0.0"
|
| 689 |
+
}
|
| 690 |
+
},
|
| 691 |
+
"node_modules/ndarray-pixels": {
|
| 692 |
+
"version": "5.2.0",
|
| 693 |
+
"resolved": "https://registry.npmjs.org/ndarray-pixels/-/ndarray-pixels-5.2.0.tgz",
|
| 694 |
+
"integrity": "sha512-lTh4tFKziAatVTa9crIsidUyn+lqujVOQpzfdBWvdFu2wo9Uo6z261lVX7SgMyP89xGmj3TMTPbbxl9YDnV4SA==",
|
| 695 |
+
"license": "MIT",
|
| 696 |
+
"dependencies": {
|
| 697 |
+
"@types/ndarray": "^1.0.14",
|
| 698 |
+
"ndarray": "^1.0.19",
|
| 699 |
+
"ndarray-ops": "^1.2.2",
|
| 700 |
+
"sharp": "^0.35.0"
|
| 701 |
+
}
|
| 702 |
+
},
|
| 703 |
+
"node_modules/property-graph": {
|
| 704 |
+
"version": "4.1.0",
|
| 705 |
+
"resolved": "https://registry.npmjs.org/property-graph/-/property-graph-4.1.0.tgz",
|
| 706 |
+
"integrity": "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ==",
|
| 707 |
+
"license": "MIT"
|
| 708 |
+
},
|
| 709 |
+
"node_modules/semver": {
|
| 710 |
+
"version": "7.8.6",
|
| 711 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz",
|
| 712 |
+
"integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==",
|
| 713 |
+
"license": "ISC",
|
| 714 |
+
"bin": {
|
| 715 |
+
"semver": "bin/semver.js"
|
| 716 |
+
},
|
| 717 |
+
"engines": {
|
| 718 |
+
"node": ">=10"
|
| 719 |
+
}
|
| 720 |
+
},
|
| 721 |
+
"node_modules/sharp": {
|
| 722 |
+
"version": "0.35.5",
|
| 723 |
+
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz",
|
| 724 |
+
"integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==",
|
| 725 |
+
"license": "Apache-2.0",
|
| 726 |
+
"dependencies": {
|
| 727 |
+
"@img/colour": "^1.1.0",
|
| 728 |
+
"detect-libc": "^2.1.2",
|
| 729 |
+
"semver": "^7.8.5"
|
| 730 |
+
},
|
| 731 |
+
"engines": {
|
| 732 |
+
"node": ">=20.9.0"
|
| 733 |
+
},
|
| 734 |
+
"funding": {
|
| 735 |
+
"url": "https://opencollective.com/libvips"
|
| 736 |
+
},
|
| 737 |
+
"optionalDependencies": {
|
| 738 |
+
"@img/sharp-darwin-arm64": "0.35.5",
|
| 739 |
+
"@img/sharp-darwin-x64": "0.35.5",
|
| 740 |
+
"@img/sharp-freebsd-wasm32": "0.35.5",
|
| 741 |
+
"@img/sharp-libvips-darwin-arm64": "1.3.4",
|
| 742 |
+
"@img/sharp-libvips-darwin-x64": "1.3.4",
|
| 743 |
+
"@img/sharp-libvips-linux-arm": "1.3.4",
|
| 744 |
+
"@img/sharp-libvips-linux-arm64": "1.3.4",
|
| 745 |
+
"@img/sharp-libvips-linux-ppc64": "1.3.4",
|
| 746 |
+
"@img/sharp-libvips-linux-riscv64": "1.3.4",
|
| 747 |
+
"@img/sharp-libvips-linux-s390x": "1.3.4",
|
| 748 |
+
"@img/sharp-libvips-linux-x64": "1.3.4",
|
| 749 |
+
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4",
|
| 750 |
+
"@img/sharp-libvips-linuxmusl-x64": "1.3.4",
|
| 751 |
+
"@img/sharp-linux-arm": "0.35.5",
|
| 752 |
+
"@img/sharp-linux-arm64": "0.35.5",
|
| 753 |
+
"@img/sharp-linux-ppc64": "0.35.5",
|
| 754 |
+
"@img/sharp-linux-riscv64": "0.35.5",
|
| 755 |
+
"@img/sharp-linux-s390x": "0.35.5",
|
| 756 |
+
"@img/sharp-linux-x64": "0.35.5",
|
| 757 |
+
"@img/sharp-linuxmusl-arm64": "0.35.5",
|
| 758 |
+
"@img/sharp-linuxmusl-x64": "0.35.5",
|
| 759 |
+
"@img/sharp-webcontainers-wasm32": "0.35.5",
|
| 760 |
+
"@img/sharp-win32-arm64": "0.35.5",
|
| 761 |
+
"@img/sharp-win32-ia32": "0.35.5",
|
| 762 |
+
"@img/sharp-win32-x64": "0.35.5"
|
| 763 |
+
},
|
| 764 |
+
"peerDependenciesMeta": {
|
| 765 |
+
"@types/node": {
|
| 766 |
+
"optional": true
|
| 767 |
+
}
|
| 768 |
+
}
|
| 769 |
+
},
|
| 770 |
+
"node_modules/tslib": {
|
| 771 |
+
"version": "2.9.1",
|
| 772 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz",
|
| 773 |
+
"integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==",
|
| 774 |
+
"license": "0BSD",
|
| 775 |
+
"optional": true
|
| 776 |
+
},
|
| 777 |
+
"node_modules/uniq": {
|
| 778 |
+
"version": "1.0.1",
|
| 779 |
+
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
| 780 |
+
"integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==",
|
| 781 |
+
"license": "MIT"
|
| 782 |
+
}
|
| 783 |
+
}
|
| 784 |
+
}
|
recipe/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"private":true,"type":"module","dependencies":{"@gltf-transform/core":"4.5.1","@gltf-transform/extensions":"4.5.1","@gltf-transform/functions":"4.5.1","meshoptimizer":"1.3.0"}}
|
recipe/pipeline.py
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ABO mesh-proxy continuation of Forma3D Tiny 2. No large teacher models run."""
|
| 2 |
+
import argparse
|
| 3 |
+
import fcntl
|
| 4 |
+
import getpass
|
| 5 |
+
import hashlib
|
| 6 |
+
import importlib.util
|
| 7 |
+
import importlib.metadata
|
| 8 |
+
import json
|
| 9 |
+
import math
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import random
|
| 12 |
+
import re
|
| 13 |
+
import shutil
|
| 14 |
+
import subprocess
|
| 15 |
+
import sys
|
| 16 |
+
import time
|
| 17 |
+
|
| 18 |
+
BASE = 'Lucasgitguy-co/forma3d-tiny-2'
|
| 19 |
+
BASE_REV = '1648582b07a0f7f9d482a8c0f4cf89e6ecd86972'
|
| 20 |
+
DATA = 'codepawl/abo-glb'
|
| 21 |
+
DATA_REV = 'c7c8293fed3b7ab122ef5d7896521a187e0962d7'
|
| 22 |
+
DEST = 'Lucasgitguy-co/forma3d-tiny-3'
|
| 23 |
+
HERE = Path(__file__).resolve().parent
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def dump(path, value):
|
| 27 |
+
path = Path(path)
|
| 28 |
+
tmp = path.with_suffix(path.suffix + '.tmp')
|
| 29 |
+
tmp.write_text(json.dumps(value, indent=2, allow_nan=False) + '\n')
|
| 30 |
+
tmp.replace(path)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def sha(path):
|
| 34 |
+
h = hashlib.sha256()
|
| 35 |
+
with Path(path).open('rb') as f:
|
| 36 |
+
for chunk in iter(lambda: f.read(1024 * 1024), b''):
|
| 37 |
+
h.update(chunk)
|
| 38 |
+
return h.hexdigest()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def read(path):
|
| 42 |
+
return json.loads(Path(path).read_text())
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def log(**kw):
|
| 46 |
+
print(json.dumps(kw, allow_nan=False), flush=True)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def authenticate():
|
| 50 |
+
from huggingface_hub import HfApi, get_token, login
|
| 51 |
+
token = get_token()
|
| 52 |
+
if not token:
|
| 53 |
+
print('Enter a Hugging Face WRITE token for Lucasgitguy-co. Input is hidden.\n'
|
| 54 |
+
'Create one at https://huggingface.co/settings/tokens', flush=True)
|
| 55 |
+
token = getpass.getpass('Hugging Face token: ')
|
| 56 |
+
login(token=token, add_to_git_credential=False)
|
| 57 |
+
api = HfApi(token=token)
|
| 58 |
+
identity = api.whoami()
|
| 59 |
+
owner = DEST.split('/')[0]
|
| 60 |
+
valid_owner = identity['name'].lower() == owner.lower()
|
| 61 |
+
valid_org = any(o['name'].lower() == owner.lower() and o.get('roleInOrg') in ('admin', 'write')
|
| 62 |
+
for o in identity.get('orgs', []))
|
| 63 |
+
if not (valid_owner or valid_org):
|
| 64 |
+
raise RuntimeError(f'This login cannot publish under {owner}. Use a token for that account.')
|
| 65 |
+
if identity.get('auth', {}).get('accessToken', {}).get('role') == 'read':
|
| 66 |
+
raise RuntimeError('This is a read-only token. Use a write token.')
|
| 67 |
+
print('Hugging Face login verified. No repository is created until evaluation passes.', flush=True)
|
| 68 |
+
return api
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def upstream(root):
|
| 72 |
+
from huggingface_hub import snapshot_download
|
| 73 |
+
source = root / 'upstream'
|
| 74 |
+
snapshot_download(BASE, revision=BASE_REV, local_dir=source,
|
| 75 |
+
allow_patterns=['src/*', 'scripts/prepare_hf.py', 'model.pt', 'config.json',
|
| 76 |
+
'pyproject.toml', 'LICENSE', 'NOTICE', 'THIRD_PARTY.md',
|
| 77 |
+
'licenses/*', 'ATTRIBUTIONS.json', 'README.md'], max_workers=2)
|
| 78 |
+
sys.path.insert(0, str(source / 'src'))
|
| 79 |
+
spec = importlib.util.spec_from_file_location('base_prepare', source / 'scripts/prepare_hf.py')
|
| 80 |
+
mod = importlib.util.module_from_spec(spec)
|
| 81 |
+
spec.loader.exec_module(mod)
|
| 82 |
+
return source, mod
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def prepare(root, source, helper, args):
|
| 86 |
+
import numpy as np
|
| 87 |
+
from huggingface_hub import hf_hub_download
|
| 88 |
+
from image3d_student.data import cache_sample, CachedDataset
|
| 89 |
+
from image3d_student.splats import Splats, preview
|
| 90 |
+
from image3d_student.engine import split_rows
|
| 91 |
+
folder = root / 'data'
|
| 92 |
+
folder.mkdir(exist_ok=True)
|
| 93 |
+
ready = folder / 'preparation.json'
|
| 94 |
+
if ready.exists():
|
| 95 |
+
for split in ('train', 'validation', 'test'):
|
| 96 |
+
CachedDataset(folder / f'{split}.jsonl')
|
| 97 |
+
print('Reusing verified prepared data.', flush=True)
|
| 98 |
+
return folder
|
| 99 |
+
metadata = hf_hub_download(DATA, 'metadata.jsonl', repo_type='dataset', revision=DATA_REV,
|
| 100 |
+
local_dir=root / 'metadata')
|
| 101 |
+
license_path = hf_hub_download(DATA, 'LICENSE-CC-BY-4.0.txt', repo_type='dataset',
|
| 102 |
+
revision=DATA_REV, local_dir=root / 'metadata')
|
| 103 |
+
shutil.copy2(license_path, folder / 'LICENSE-ABO-CC-BY-4.0.txt')
|
| 104 |
+
records = [json.loads(line) for line in Path(metadata).read_text().splitlines() if line.strip()]
|
| 105 |
+
candidates = [r for r in records if 0 < r.get('triangles', 0) <= 200_000
|
| 106 |
+
and 0 < r.get('pack_bytes', 0) <= 8 * 1024**2
|
| 107 |
+
and re.fullmatch(r'[A-Za-z0-9_-]+', r['id'])]
|
| 108 |
+
candidates.sort(key=lambda r: r['id'])
|
| 109 |
+
random.Random(args.seed).shuffle(candidates)
|
| 110 |
+
progress = folder / 'progress.json'
|
| 111 |
+
state = read(progress) if progress.exists() else {'rows': [], 'sources': [], 'skipped': []}
|
| 112 |
+
rows, sources = state['rows'], state['sources']
|
| 113 |
+
completed = {s['asset_id'] for s in sources}
|
| 114 |
+
prior = read(source / 'ATTRIBUTIONS.json')
|
| 115 |
+
blocked_hashes = set()
|
| 116 |
+
def visit(x):
|
| 117 |
+
if isinstance(x, dict):
|
| 118 |
+
for k, v in x.items():
|
| 119 |
+
if k == 'sha256' and isinstance(v, str):
|
| 120 |
+
blocked_hashes.add(v)
|
| 121 |
+
visit(v)
|
| 122 |
+
elif isinstance(x, list):
|
| 123 |
+
for v in x:
|
| 124 |
+
visit(v)
|
| 125 |
+
visit(prior)
|
| 126 |
+
seen_hashes = {s['sha256'] for s in sources} | blocked_hashes
|
| 127 |
+
fetch = helper.Downloader(root / 'downloads', 8, 3072, 4)
|
| 128 |
+
for record in candidates:
|
| 129 |
+
if len(sources) >= args.objects:
|
| 130 |
+
break
|
| 131 |
+
uid = record['id']
|
| 132 |
+
if uid in completed:
|
| 133 |
+
continue
|
| 134 |
+
stage = folder / '_staging'
|
| 135 |
+
if stage.exists():
|
| 136 |
+
shutil.rmtree(stage)
|
| 137 |
+
stage.mkdir()
|
| 138 |
+
try:
|
| 139 |
+
filename = f'objects/{uid}/{uid}.glb'
|
| 140 |
+
raw = fetch.get(DATA, DATA_REV, filename)
|
| 141 |
+
digest = sha(raw)
|
| 142 |
+
if digest in seen_hashes:
|
| 143 |
+
continue
|
| 144 |
+
decoded = stage / 'decoded.glb'
|
| 145 |
+
subprocess.run(['node', str(HERE / 'decode.mjs'), str(raw), str(decoded)],
|
| 146 |
+
check=True, capture_output=True, text=True, timeout=120)
|
| 147 |
+
sample_seed = int(digest[:8], 16) ^ args.seed
|
| 148 |
+
splats, normalization = helper.mesh_splats(decoded, 4096, 16, sample_seed)
|
| 149 |
+
info = {'repo': DATA, 'revision': DATA_REV, 'path': filename, 'sha256': digest,
|
| 150 |
+
'decoded_sha256': sha(decoded), 'asset_id': uid, 'object_id': 'abo-' + uid,
|
| 151 |
+
'amazon_item_ids': record.get('amazon_item_ids', []),
|
| 152 |
+
'title': record.get('name', uid), 'product_type': record.get('product_type'),
|
| 153 |
+
'author': 'Amazon.com; Amazon Berkeley Objects contributors',
|
| 154 |
+
'conversion': 'CodePawl GLB conversion; decompressed, normalized and sampled for training',
|
| 155 |
+
'license': 'cc-by-4.0', 'license_url': 'https://creativecommons.org/licenses/by/4.0/',
|
| 156 |
+
'source_url': f'https://huggingface.co/datasets/{DATA}/blob/{DATA_REV}/{filename}'}
|
| 157 |
+
new_rows = []
|
| 158 |
+
for view in range(4):
|
| 159 |
+
yaw = 2 * np.pi * view / 4
|
| 160 |
+
c, s = np.cos(yaw), np.sin(yaw)
|
| 161 |
+
rotation = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]], np.float32)
|
| 162 |
+
rotated = Splats(splats.xyz @ rotation.T, splats.rgb, splats.scale,
|
| 163 |
+
splats.quat, splats.opacity)
|
| 164 |
+
picture = preview(rotated, size=128)
|
| 165 |
+
if picture.getbbox() is None:
|
| 166 |
+
raise ValueError('Invisible proxy render')
|
| 167 |
+
picture.save(stage / 'input.png')
|
| 168 |
+
new_rows.append(cache_sample(stage, f'abo-{uid}-v{view}', stage / 'input.png',
|
| 169 |
+
rotated, 16, source='mesh-proxy:' + DATA,
|
| 170 |
+
extra={**info, 'normalization_matrix': normalization, 'yaw_radians': float(yaw),
|
| 171 |
+
'render': 'orthographic Gaussian proxy on black; camera +z',
|
| 172 |
+
'target': 'mesh-derived isotropic surface samples; no pretrained teacher'}))
|
| 173 |
+
for name in ('images', 'targets'):
|
| 174 |
+
(folder / name).mkdir(exist_ok=True)
|
| 175 |
+
for p in (stage / name).iterdir():
|
| 176 |
+
p.replace(folder / name / p.name)
|
| 177 |
+
rows.extend(new_rows)
|
| 178 |
+
sources.append(info)
|
| 179 |
+
seen_hashes.add(digest)
|
| 180 |
+
completed.add(uid)
|
| 181 |
+
dump(progress, state)
|
| 182 |
+
log(prepared_objects=len(sources), requested=args.objects, asset=uid)
|
| 183 |
+
except RuntimeError:
|
| 184 |
+
raise
|
| 185 |
+
except Exception as exc:
|
| 186 |
+
error = str(exc)[:300]
|
| 187 |
+
if isinstance(exc, subprocess.CalledProcessError):
|
| 188 |
+
error = exc.stderr[-500:]
|
| 189 |
+
state['skipped'].append({'id': uid, 'error': error})
|
| 190 |
+
dump(progress, state)
|
| 191 |
+
log(skipped=uid, error=error)
|
| 192 |
+
if len(state['skipped']) >= 20 and not sources:
|
| 193 |
+
raise RuntimeError('Twenty objects failed. Inspect data/progress.json before continuing.') from exc
|
| 194 |
+
finally:
|
| 195 |
+
shutil.rmtree(stage, ignore_errors=True)
|
| 196 |
+
if len(sources) < args.objects:
|
| 197 |
+
raise RuntimeError(f'Only {len(sources)} usable objects; need {args.objects}. Progress is saved.')
|
| 198 |
+
# Connect duplicate objects, shared Amazon item IDs and exact rendered data before splitting.
|
| 199 |
+
parent = {s['object_id']: s['object_id'] for s in sources}
|
| 200 |
+
def find(x):
|
| 201 |
+
while parent[x] != x:
|
| 202 |
+
parent[x] = parent[parent[x]]
|
| 203 |
+
x = parent[x]
|
| 204 |
+
return x
|
| 205 |
+
def union(a, b):
|
| 206 |
+
a, b = find(a), find(b)
|
| 207 |
+
parent[max(a, b)] = min(a, b)
|
| 208 |
+
identities = {}
|
| 209 |
+
for info in sources:
|
| 210 |
+
for key in [('mesh', info['sha256'])] + [('amazon', a) for a in info['amazon_item_ids']]:
|
| 211 |
+
if key in identities:
|
| 212 |
+
union(info['object_id'], identities[key])
|
| 213 |
+
identities[key] = info['object_id']
|
| 214 |
+
for row in rows:
|
| 215 |
+
for field in ('image_sha256', 'target_sha256'):
|
| 216 |
+
key = (field, row[field])
|
| 217 |
+
if key in identities:
|
| 218 |
+
union(row['metadata']['object_id'], identities[key])
|
| 219 |
+
identities[key] = row['metadata']['object_id']
|
| 220 |
+
for row in rows:
|
| 221 |
+
row['metadata']['object_id'] = find(row['metadata']['object_id'])
|
| 222 |
+
for info in sources:
|
| 223 |
+
info['object_id'] = find(info['object_id'])
|
| 224 |
+
development, test = split_rows(rows, .15, args.seed + 1)
|
| 225 |
+
training, validation = split_rows(development, .20, args.seed + 2)
|
| 226 |
+
splits = {'train': training, 'validation': validation, 'test': test}
|
| 227 |
+
for name, items in splits.items():
|
| 228 |
+
(folder / f'{name}.jsonl').write_text(''.join(json.dumps(r) + '\n' for r in items))
|
| 229 |
+
CachedDataset(folder / f'{name}.jsonl')
|
| 230 |
+
dump(folder / 'sources.json', sources)
|
| 231 |
+
dump(ready, {'dataset': DATA, 'revision': DATA_REV, 'objects': len(sources), 'views': len(rows),
|
| 232 |
+
'groups': {k: len({r['metadata']['object_id'] for r in v}) for k, v in splits.items()},
|
| 233 |
+
'split_views': {k: len(v) for k, v in splits.items()},
|
| 234 |
+
'manifest_sha256': {k: sha(folder / f'{k}.jsonl') for k in splits},
|
| 235 |
+
'grouping': 'object IDs, shared Amazon item IDs, exact mesh/image/target hashes',
|
| 236 |
+
'limitations': 'Near duplicates can remain. Cross-dataset overlap with ancestor training '
|
| 237 |
+
'is not fully known; only recorded exact source hashes were excluded.'})
|
| 238 |
+
return folder
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def cpu_tree(value):
|
| 242 |
+
import torch
|
| 243 |
+
if isinstance(value, torch.Tensor):
|
| 244 |
+
return value.detach().cpu()
|
| 245 |
+
if isinstance(value, dict):
|
| 246 |
+
return {k: cpu_tree(v) for k, v in value.items()}
|
| 247 |
+
if isinstance(value, list):
|
| 248 |
+
return [cpu_tree(v) for v in value]
|
| 249 |
+
if isinstance(value, tuple):
|
| 250 |
+
return tuple(cpu_tree(v) for v in value)
|
| 251 |
+
return value
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def train(root, source, folder, args):
|
| 255 |
+
import torch
|
| 256 |
+
from torch.utils.data import DataLoader, default_collate
|
| 257 |
+
from image3d_student.data import CachedDataset
|
| 258 |
+
from image3d_student.model import Student, distillation_loss
|
| 259 |
+
from image3d_student.engine import device_for, evaluate, on_device
|
| 260 |
+
device = device_for(args.device)
|
| 261 |
+
torch.manual_seed(args.seed)
|
| 262 |
+
run = root / 'training'
|
| 263 |
+
run.mkdir(exist_ok=True)
|
| 264 |
+
train_data = CachedDataset(folder / 'train.jsonl')
|
| 265 |
+
val_data = CachedDataset(folder / 'validation.jsonl')
|
| 266 |
+
val_loader = DataLoader(val_data, batch_size=1, num_workers=0)
|
| 267 |
+
base = torch.load(source / 'model.pt', map_location='cpu', weights_only=True)
|
| 268 |
+
if base['config'] != {'grid': 16, 'width': 64} or base['format'] != 1:
|
| 269 |
+
raise RuntimeError('Unexpected base model architecture.')
|
| 270 |
+
model = Student(**base['config']).to(device)
|
| 271 |
+
model.load_state_dict(base['model'], strict=True)
|
| 272 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
|
| 273 |
+
rng = random.Random(args.seed)
|
| 274 |
+
meta = {'base_model': BASE, 'base_revision': BASE_REV, 'base_sha256': sha(source / 'model.pt'),
|
| 275 |
+
'parameters': sum(p.numel() for p in model.parameters()), 'device': str(device),
|
| 276 |
+
'torch': str(torch.__version__), 'dataset': DATA, 'dataset_revision': DATA_REV,
|
| 277 |
+
'steps_requested': args.steps, 'batch_size': args.batch_size, 'learning_rate': args.lr,
|
| 278 |
+
'seed': args.seed, 'teacher_models_run': [], 'contains_synthetic': False,
|
| 279 |
+
'rendered_mesh_proxies': True, 'optimizer': 'fresh AdamW, resumed on interruption',
|
| 280 |
+
'data': read(folder / 'preparation.json')}
|
| 281 |
+
step, best_step = 0, 0
|
| 282 |
+
if (run / 'last.pt').exists():
|
| 283 |
+
last = torch.load(run / 'last.pt', map_location='cpu', weights_only=True)
|
| 284 |
+
model.load_state_dict(last['model'])
|
| 285 |
+
optimizer.load_state_dict(last['optimizer'])
|
| 286 |
+
rng.setstate(last['python_rng'])
|
| 287 |
+
step, best_step, best_loss = last['step'], last['best_step'], last['best_loss']
|
| 288 |
+
meta = last['metadata']
|
| 289 |
+
log(resuming_from_step=step)
|
| 290 |
+
else:
|
| 291 |
+
meta['baseline_validation'] = evaluate(model, val_loader, device)
|
| 292 |
+
best_loss = meta['baseline_validation']['loss']
|
| 293 |
+
if not math.isfinite(best_loss):
|
| 294 |
+
raise RuntimeError('Nonfinite baseline evaluation.')
|
| 295 |
+
dump(run / 'training.json', meta)
|
| 296 |
+
started = time.monotonic()
|
| 297 |
+
starting_step = step
|
| 298 |
+
def save(path, include_optimizer):
|
| 299 |
+
payload = {'format': 1, 'config': model.config, 'model': cpu_tree(model.state_dict()),
|
| 300 |
+
'step': step, 'metadata': meta}
|
| 301 |
+
if include_optimizer:
|
| 302 |
+
payload.update(optimizer=cpu_tree(optimizer.state_dict()), python_rng=rng.getstate(),
|
| 303 |
+
best_step=best_step, best_loss=best_loss)
|
| 304 |
+
tmp = path.with_suffix('.tmp')
|
| 305 |
+
torch.save(payload, tmp)
|
| 306 |
+
tmp.replace(path)
|
| 307 |
+
try:
|
| 308 |
+
while step < args.steps:
|
| 309 |
+
images, target = default_collate([train_data[rng.randrange(len(train_data))]
|
| 310 |
+
for _ in range(args.batch_size)])
|
| 311 |
+
model.train()
|
| 312 |
+
optimizer.zero_grad(set_to_none=True)
|
| 313 |
+
prediction = model(images.to(device))
|
| 314 |
+
loss, _ = distillation_loss(prediction, on_device(target, device), model.grid)
|
| 315 |
+
if not torch.isfinite(loss):
|
| 316 |
+
raise RuntimeError('Nonfinite training loss; publication disabled.')
|
| 317 |
+
loss.backward()
|
| 318 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), 1., error_if_nonfinite=True)
|
| 319 |
+
optimizer.step()
|
| 320 |
+
step += 1
|
| 321 |
+
if step == 1 or step % 50 == 0:
|
| 322 |
+
log(step=step, total=args.steps, loss=float(loss.detach()),
|
| 323 |
+
elapsed_seconds=round(time.monotonic() - started),
|
| 324 |
+
steps_per_second=round((step - starting_step) / max(time.monotonic()-started, .001), 3))
|
| 325 |
+
if step % args.eval_every == 0 or step == args.steps:
|
| 326 |
+
metrics = evaluate(model, val_loader, device)
|
| 327 |
+
if not all(math.isfinite(v) for v in metrics.values()):
|
| 328 |
+
raise RuntimeError('Nonfinite validation metrics.')
|
| 329 |
+
if metrics['loss'] < best_loss:
|
| 330 |
+
best_loss, best_step = metrics['loss'], step
|
| 331 |
+
save(run / 'best.pt', False)
|
| 332 |
+
record = {'step': step, 'validation': metrics, 'best_step': best_step}
|
| 333 |
+
with (run / 'metrics.jsonl').open('a') as f:
|
| 334 |
+
f.write(json.dumps(record) + '\n')
|
| 335 |
+
log(**record)
|
| 336 |
+
save(run / 'last.pt', True)
|
| 337 |
+
finally:
|
| 338 |
+
if step:
|
| 339 |
+
save(run / 'last.pt', True)
|
| 340 |
+
if step != args.steps:
|
| 341 |
+
raise RuntimeError('Training incomplete; no publication.')
|
| 342 |
+
if not best_step:
|
| 343 |
+
raise RuntimeError('Validation did not improve over v2. Checkpoints are local; no publication.')
|
| 344 |
+
dump(run / 'completed.json', {'completed_steps': step, 'selected_step': best_step,
|
| 345 |
+
'best_validation_loss': best_loss})
|
| 346 |
+
return run
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def assess(root, source, folder, run, args):
|
| 350 |
+
import torch
|
| 351 |
+
from torch.utils.data import DataLoader
|
| 352 |
+
from image3d_student.data import CachedDataset
|
| 353 |
+
from image3d_student.model import Student
|
| 354 |
+
from image3d_student.engine import device_for, evaluate
|
| 355 |
+
device = device_for(args.device)
|
| 356 |
+
loader = DataLoader(CachedDataset(folder / 'test.jsonl'), batch_size=1, num_workers=0)
|
| 357 |
+
result = {}
|
| 358 |
+
for name, checkpoint in [('base_v2', source / 'model.pt'), ('candidate_v3', run / 'best.pt')]:
|
| 359 |
+
state = torch.load(checkpoint, map_location='cpu', weights_only=True)
|
| 360 |
+
model = Student(**state['config']).to(device)
|
| 361 |
+
model.load_state_dict(state['model'], strict=True)
|
| 362 |
+
metrics = evaluate(model, loader, device)
|
| 363 |
+
if not all(math.isfinite(v) for v in metrics.values()):
|
| 364 |
+
raise RuntimeError('Nonfinite test metrics.')
|
| 365 |
+
result[name] = {'sha256': sha(checkpoint), 'metrics': metrics}
|
| 366 |
+
del model
|
| 367 |
+
before, after = result['base_v2']['metrics'], result['candidate_v3']['metrics']
|
| 368 |
+
meta = read(run / 'training.json')
|
| 369 |
+
completed = read(run / 'completed.json')
|
| 370 |
+
gates = {'training_complete': completed['completed_steps'] == args.steps,
|
| 371 |
+
'validation_loss_improved': completed['best_validation_loss'] < meta['baseline_validation']['loss'],
|
| 372 |
+
'test_loss_improved_at_least_half_percent': after['loss'] <= before['loss'] * .995,
|
| 373 |
+
'test_occupancy_iou_not_worse': after['occupancy_iou'] >= before['occupancy_iou'],
|
| 374 |
+
'weights_changed': result['base_v2']['sha256'] != result['candidate_v3']['sha256']}
|
| 375 |
+
report = {**result, 'gates': gates, 'passed': all(gates.values()),
|
| 376 |
+
'test_manifest_sha256': sha(folder / 'test.jsonl'),
|
| 377 |
+
'measurement': 'mean per-view proxy loss and occupancy IoU, threshold 0.5, batch size 1',
|
| 378 |
+
'limitation': 'Not evidence of natural-photo, mesh, texture or photorealistic rendering quality.'}
|
| 379 |
+
dump(root / 'evaluation.json', report)
|
| 380 |
+
log(evaluation=report)
|
| 381 |
+
return report
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def package(root, source, folder, run, report):
|
| 385 |
+
release = root / 'release'
|
| 386 |
+
release.mkdir(exist_ok=True)
|
| 387 |
+
shutil.copy2(run / 'best.pt', release / 'model.pt')
|
| 388 |
+
for name in ('LICENSE', 'NOTICE', 'THIRD_PARTY.md', 'config.json'):
|
| 389 |
+
shutil.copy2(source / name, release / name)
|
| 390 |
+
for name in ('src', 'licenses'):
|
| 391 |
+
shutil.copytree(source / name, release / name, dirs_exist_ok=True,
|
| 392 |
+
ignore=shutil.ignore_patterns('__pycache__', '*.pyc'))
|
| 393 |
+
project = (source / 'pyproject.toml').read_text().replace('forma3d-tiny-2', 'forma3d-tiny-3')
|
| 394 |
+
project = project.replace('version = "0.3.0"', 'version = "0.4.0"')
|
| 395 |
+
(release / 'pyproject.toml').write_text(project)
|
| 396 |
+
shutil.copy2(folder / 'LICENSE-ABO-CC-BY-4.0.txt', release / 'licenses/LICENSE-ABO-CC-BY-4.0.txt')
|
| 397 |
+
for path in (run / 'training.json', run / 'completed.json', run / 'metrics.jsonl',
|
| 398 |
+
folder / 'preparation.json', root / 'evaluation.json', root / 'settings.json'):
|
| 399 |
+
shutil.copy2(path, release / path.name)
|
| 400 |
+
dump(release / 'ATTRIBUTIONS.json', {'inherited': read(source / 'ATTRIBUTIONS.json'),
|
| 401 |
+
'abo_continuation': read(folder / 'sources.json')})
|
| 402 |
+
(release / 'splits').mkdir(exist_ok=True)
|
| 403 |
+
for name in ('train', 'validation', 'test'):
|
| 404 |
+
shutil.copy2(folder / f'{name}.jsonl', release / 'splits' / f'{name}.jsonl')
|
| 405 |
+
recipe = release / 'recipe'
|
| 406 |
+
recipe.mkdir(exist_ok=True)
|
| 407 |
+
for name in ('pipeline.py', 'decode.mjs', 'requirements.txt', 'package.json', 'package-lock.json', 'run.command'):
|
| 408 |
+
shutil.copy2(HERE / name, recipe / name)
|
| 409 |
+
inherited_notice = (release / 'NOTICE').read_text()
|
| 410 |
+
(release / 'NOTICE').write_text(inherited_notice + '\nABO continuation data: Amazon.com, Amazon Berkeley '
|
| 411 |
+
'Objects contributors; converted to GLB by CodePawl. CC BY 4.0. Training preprocessing '
|
| 412 |
+
'decompresses geometry, normalizes objects, samples surface colors and renders Gaussian proxies. '
|
| 413 |
+
'See ATTRIBUTIONS.json and licenses/LICENSE-ABO-CC-BY-4.0.txt. No endorsement implied.\n')
|
| 414 |
+
base = report['base_v2']['metrics']
|
| 415 |
+
candidate = report['candidate_v3']['metrics']
|
| 416 |
+
prep = read(folder / 'preparation.json')
|
| 417 |
+
completed = read(run / 'completed.json')
|
| 418 |
+
(release / 'README.md').write_text(f'''---
|
| 419 |
+
license: apache-2.0
|
| 420 |
+
pipeline_tag: image-to-3d
|
| 421 |
+
base_model: {BASE}
|
| 422 |
+
base_model_relation: finetune
|
| 423 |
+
model_name: Forma3D Tiny 3
|
| 424 |
+
datasets:
|
| 425 |
+
- codepawl/abo-glb
|
| 426 |
+
tags:
|
| 427 |
+
- pytorch
|
| 428 |
+
- gaussian-splatting
|
| 429 |
+
- mesh-proxy-supervision
|
| 430 |
+
- research-prototype
|
| 431 |
+
---
|
| 432 |
+
# Forma3D Tiny 3
|
| 433 |
+
|
| 434 |
+
Continued from [{BASE}](https://huggingface.co/{BASE}) at `{BASE_REV}`.
|
| 435 |
+
Same 306,321-parameter architecture, 16 cubed grid, width 64, maximum 4,096 Gaussian slots.
|
| 436 |
+
Fresh AdamW optimizer at the start of continuation. Completed {completed['completed_steps']} steps;
|
| 437 |
+
selected step {completed['selected_step']} by validation loss only.
|
| 438 |
+
|
| 439 |
+
## Data and scope
|
| 440 |
+
|
| 441 |
+
{prep['objects']} ABO objects, {prep['views']} orthographic Gaussian-proxy images on black.
|
| 442 |
+
Data revision: `{DATA_REV}`. Targets are isotropic mesh surface samples, compressed to
|
| 443 |
+
one Gaussian per occupied grid cell. Texture surface colors are sampled; full PBR rendering
|
| 444 |
+
and differentiable rendering are not used. No natural-photo training is claimed.
|
| 445 |
+
MeshAnythingV2, Idol Diffusers, SHARP, Pixal3D, Idol Turbo and Qwen3.5-4B-3D-Coder
|
| 446 |
+
were **not run, merged, or distilled** in this Mac-only recipe.
|
| 447 |
+
|
| 448 |
+
Objects, shared Amazon item IDs and exact mesh/image/target hashes are grouped before splitting.
|
| 449 |
+
Split groups: {json.dumps(prep['groups'])}. Test data is excluded from training and checkpoint selection.
|
| 450 |
+
Near duplicates can remain; overlap with ancestor training datasets is not fully known.
|
| 451 |
+
|
| 452 |
+
## Evaluation
|
| 453 |
+
|
| 454 |
+
Both checkpoints were evaluated on the identical held-out ABO proxy views.
|
| 455 |
+
|
| 456 |
+
| Metric | Starting v2 | Selected v3 |
|
| 457 |
+
|---|---:|---:|
|
| 458 |
+
| Mean test loss | {base['loss']:.6f} | {candidate['loss']:.6f} |
|
| 459 |
+
| Mean view occupancy IoU, threshold 0.5 | {base['occupancy_iou']:.6f} | {candidate['occupancy_iou']:.6f} |
|
| 460 |
+
|
| 461 |
+
These measurements do not establish natural-photo reconstruction, photorealism, mesh quality,
|
| 462 |
+
novel-view rendering quality or improvement on v2's original domains. No confidence intervals
|
| 463 |
+
or broad generalization claim. See evaluation.json, preparation.json and split manifests.
|
| 464 |
+
|
| 465 |
+
## Use
|
| 466 |
+
|
| 467 |
+
With Python 3.11, install this repository using `pip install .`, then:
|
| 468 |
+
|
| 469 |
+
```bash
|
| 470 |
+
image3d generate --checkpoint model.pt --image centered-object-on-black.png --out object.ply --device mps
|
| 471 |
+
image3d preview --ply object.ply --out preview.png
|
| 472 |
+
```
|
| 473 |
+
|
| 474 |
+
Output is Gaussian PLY, not a textured GLB mesh. Inputs should match centered objects on black.
|
| 475 |
+
The model has no text prompt conditioning. Reproduction launcher and locked dependencies are in recipe/.
|
| 476 |
+
|
| 477 |
+
## Attribution
|
| 478 |
+
|
| 479 |
+
Publisher source and weights: Apache 2.0. Retain inherited NOTICE, THIRD_PARTY.md and ATTRIBUTIONS.json.
|
| 480 |
+
ABO models, images and listing text: Amazon.com and the Amazon Berkeley Objects contributors,
|
| 481 |
+
CC BY 4.0; converted to GLB by CodePawl. The recipe further decompresses geometry,
|
| 482 |
+
normalizes coordinates, samples colored surfaces and renders proxy images.
|
| 483 |
+
See [ABO dataset](https://huggingface.co/datasets/codepawl/abo-glb),
|
| 484 |
+
ATTRIBUTIONS.json and licenses/LICENSE-ABO-CC-BY-4.0.txt. Raw assets are not bundled.
|
| 485 |
+
''')
|
| 486 |
+
freeze = ''.join(sorted(f"{d.metadata['Name']}=={d.version}\n"
|
| 487 |
+
for d in importlib.metadata.distributions()))
|
| 488 |
+
(release / 'environment.txt').write_text(freeze)
|
| 489 |
+
sums = ''.join(f'{sha(p)} {p.relative_to(release)}\n' for p in sorted(release.rglob('*'))
|
| 490 |
+
if p.is_file() and p.name != 'SHA256SUMS')
|
| 491 |
+
(release / 'SHA256SUMS').write_text(sums)
|
| 492 |
+
return release
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
def publish(api, root, release):
|
| 496 |
+
from huggingface_hub import hf_hub_download
|
| 497 |
+
from huggingface_hub.errors import RepositoryNotFoundError, EntryNotFoundError
|
| 498 |
+
marker = root / 'publication.json'
|
| 499 |
+
expected = sha(release / 'model.pt')
|
| 500 |
+
try:
|
| 501 |
+
info = api.model_info(DEST)
|
| 502 |
+
except RepositoryNotFoundError:
|
| 503 |
+
info = None
|
| 504 |
+
if info is not None:
|
| 505 |
+
try:
|
| 506 |
+
remote = hf_hub_download(DEST, 'model.pt', revision=info.sha, token=api.token)
|
| 507 |
+
except EntryNotFoundError:
|
| 508 |
+
if not marker.exists():
|
| 509 |
+
raise RuntimeError(f'{DEST} already exists. Refusing to overwrite an unrelated repository.')
|
| 510 |
+
else:
|
| 511 |
+
if sha(remote) == expected:
|
| 512 |
+
print(f'Already published and verified: https://huggingface.co/{DEST}', flush=True)
|
| 513 |
+
return
|
| 514 |
+
raise RuntimeError(f'{DEST} already has different weights; refusing to overwrite them.')
|
| 515 |
+
else:
|
| 516 |
+
api.create_repo(DEST, repo_type='model', private=False, exist_ok=False)
|
| 517 |
+
dump(marker, {'repo_id': DEST, 'expected_model_sha256': expected})
|
| 518 |
+
commit = api.upload_folder(repo_id=DEST, repo_type='model', folder_path=release,
|
| 519 |
+
commit_message='Publish evaluated Forma3D Tiny 3 ABO continuation')
|
| 520 |
+
remote = hf_hub_download(DEST, 'model.pt', revision=commit.oid, token=api.token)
|
| 521 |
+
if sha(remote) != expected:
|
| 522 |
+
raise RuntimeError('Uploaded checkpoint verification failed.')
|
| 523 |
+
dump(marker, {'repo_id': DEST, 'model_sha256': expected, 'commit': commit.oid, 'verified': True})
|
| 524 |
+
print(f'Published and verified: https://huggingface.co/{DEST}', flush=True)
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def main():
|
| 528 |
+
p = argparse.ArgumentParser(description=__doc__)
|
| 529 |
+
p.add_argument('--root', type=Path, default=HERE / 'run')
|
| 530 |
+
p.add_argument('--objects', type=int, default=512)
|
| 531 |
+
p.add_argument('--steps', type=int, default=6000)
|
| 532 |
+
p.add_argument('--batch-size', type=int, default=4)
|
| 533 |
+
p.add_argument('--lr', type=float, default=5e-5)
|
| 534 |
+
p.add_argument('--eval-every', type=int, default=250)
|
| 535 |
+
p.add_argument('--seed', type=int, default=44)
|
| 536 |
+
p.add_argument('--device', choices=['mps', 'cpu'], default='mps')
|
| 537 |
+
p.add_argument('--publish', action='store_true')
|
| 538 |
+
p.add_argument('--prepare-only', action='store_true')
|
| 539 |
+
args = p.parse_args()
|
| 540 |
+
if args.objects < 12 or min(args.steps, args.batch_size, args.eval_every) < 1 or args.lr <= 0:
|
| 541 |
+
p.error('Need at least 12 objects and positive training settings.')
|
| 542 |
+
if args.publish and (args.objects < 128 or args.steps < 1000 or args.device != 'mps'):
|
| 543 |
+
p.error('Publication requires >=128 objects, >=1000 steps and MPS; small runs are tests only.')
|
| 544 |
+
root = args.root.resolve()
|
| 545 |
+
root.mkdir(parents=True, exist_ok=True)
|
| 546 |
+
with (root / '.lock').open('w') as lock:
|
| 547 |
+
try:
|
| 548 |
+
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 549 |
+
except BlockingIOError:
|
| 550 |
+
raise RuntimeError('Another run is already using this directory.')
|
| 551 |
+
settings = {k: v for k, v in vars(args).items() if k not in ('root', 'publish', 'prepare_only')}
|
| 552 |
+
settings.update(base_revision=BASE_REV, data_revision=DATA_REV, recipe_sha256=sha(__file__))
|
| 553 |
+
settings_path = root / 'settings.json'
|
| 554 |
+
if settings_path.exists() and read(settings_path) != settings:
|
| 555 |
+
raise RuntimeError('Run settings/code changed. Use --root with a new directory.')
|
| 556 |
+
dump(settings_path, settings)
|
| 557 |
+
if shutil.disk_usage(root).free < 8 * 1024**3:
|
| 558 |
+
raise RuntimeError('Need at least 8 GiB free disk space before starting.')
|
| 559 |
+
api = authenticate() if args.publish and not args.prepare_only else None
|
| 560 |
+
source, helper = upstream(root)
|
| 561 |
+
folder = prepare(root, source, helper, args)
|
| 562 |
+
if args.prepare_only:
|
| 563 |
+
return
|
| 564 |
+
run = train(root, source, folder, args)
|
| 565 |
+
report = assess(root, source, folder, run, args)
|
| 566 |
+
if not report['passed']:
|
| 567 |
+
print('Release checks did not pass. Results and checkpoints are saved locally; nothing published.')
|
| 568 |
+
sys.exit(2)
|
| 569 |
+
release = package(root, source, folder, run, report)
|
| 570 |
+
if args.publish:
|
| 571 |
+
publish(api, root, release)
|
| 572 |
+
else:
|
| 573 |
+
print(f'Local test only; no publication. Release package: {release}')
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
if __name__ == '__main__':
|
| 577 |
+
try:
|
| 578 |
+
main()
|
| 579 |
+
except KeyboardInterrupt:
|
| 580 |
+
print('\nInterrupted. Rerun the same command to continue from saved progress.', file=sys.stderr)
|
| 581 |
+
sys.exit(130)
|
recipe/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.15.0
|
| 2 |
+
numpy==2.4.6
|
| 3 |
+
pillow==13.0.0
|
| 4 |
+
plyfile==1.1.5
|
| 5 |
+
trimesh==5.2.0
|
| 6 |
+
scipy==1.17.1
|
| 7 |
+
huggingface-hub==2.4.0
|
| 8 |
+
pyarrow==26.0.0
|
recipe/run.command
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
cd -- "$(dirname -- "$0")"
|
| 4 |
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
| 5 |
+
if [[ "$(uname -s)" != Darwin || "$(uname -m)" != arm64 ]]; then
|
| 6 |
+
echo 'Run this launcher in a native Apple Silicon macOS Terminal.'; exit 1
|
| 7 |
+
fi
|
| 8 |
+
if ! command -v uv >/dev/null || ! command -v node >/dev/null || ! command -v npm >/dev/null; then
|
| 9 |
+
echo 'Missing prerequisites. Install uv and Node.js, then rerun. With Homebrew: brew install uv node'
|
| 10 |
+
exit 1
|
| 11 |
+
fi
|
| 12 |
+
node -e 'if(Number(process.versions.node.split(".")[0])<20)process.exit(1)' || {
|
| 13 |
+
echo 'Node.js 20 or later is required.'; exit 1;
|
| 14 |
+
}
|
| 15 |
+
export UV_CACHE_DIR="$PWD/.cache/uv"
|
| 16 |
+
export HF_HOME="$PWD/.cache/huggingface"
|
| 17 |
+
export PYTHONUNBUFFERED=1
|
| 18 |
+
export OMP_NUM_THREADS=4
|
| 19 |
+
export PYTORCH_ENABLE_MPS_FALLBACK=1
|
| 20 |
+
uv venv --python 3.11 --allow-existing .venv
|
| 21 |
+
uv pip install --python .venv/bin/python -r requirements.txt
|
| 22 |
+
if [[ ! -d node_modules ]]; then
|
| 23 |
+
npm ci --ignore-scripts --no-audit --no-fund
|
| 24 |
+
fi
|
| 25 |
+
echo 'Preparing 512 ABO objects, training 6,000 steps, then evaluating and publishing if the release checks pass.'
|
| 26 |
+
echo 'Keep your Mac connected to power and its lid open. Rerun this same command after an interruption.'
|
| 27 |
+
exec caffeinate -i .venv/bin/python pipeline.py --publish "$@"
|
settings.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"objects": 512,
|
| 3 |
+
"steps": 6000,
|
| 4 |
+
"batch_size": 4,
|
| 5 |
+
"lr": 5e-05,
|
| 6 |
+
"eval_every": 250,
|
| 7 |
+
"seed": 44,
|
| 8 |
+
"device": "mps",
|
| 9 |
+
"base_revision": "1648582b07a0f7f9d482a8c0f4cf89e6ecd86972",
|
| 10 |
+
"data_revision": "c7c8293fed3b7ab122ef5d7896521a187e0962d7",
|
| 11 |
+
"recipe_sha256": "33ef603b3550a78eb982138540bcdbbca596d3b3f697368593a6d5ca3ff4b436"
|
| 12 |
+
}
|
splits/test.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
splits/train.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
splits/validation.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
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)
|
training.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_model": "Lucasgitguy-co/forma3d-tiny-2",
|
| 3 |
+
"base_revision": "1648582b07a0f7f9d482a8c0f4cf89e6ecd86972",
|
| 4 |
+
"base_sha256": "c051b3f93d874f7c638588c9d0381754d3f954b1cd271121a033b2478d36bb1a",
|
| 5 |
+
"parameters": 306321,
|
| 6 |
+
"device": "mps",
|
| 7 |
+
"torch": "2.15.0",
|
| 8 |
+
"dataset": "codepawl/abo-glb",
|
| 9 |
+
"dataset_revision": "c7c8293fed3b7ab122ef5d7896521a187e0962d7",
|
| 10 |
+
"steps_requested": 6000,
|
| 11 |
+
"batch_size": 4,
|
| 12 |
+
"learning_rate": 5e-05,
|
| 13 |
+
"seed": 44,
|
| 14 |
+
"teacher_models_run": [],
|
| 15 |
+
"contains_synthetic": false,
|
| 16 |
+
"rendered_mesh_proxies": true,
|
| 17 |
+
"optimizer": "fresh AdamW, resumed on interruption",
|
| 18 |
+
"data": {
|
| 19 |
+
"dataset": "codepawl/abo-glb",
|
| 20 |
+
"revision": "c7c8293fed3b7ab122ef5d7896521a187e0962d7",
|
| 21 |
+
"objects": 512,
|
| 22 |
+
"views": 2048,
|
| 23 |
+
"groups": {
|
| 24 |
+
"train": 348,
|
| 25 |
+
"validation": 87,
|
| 26 |
+
"test": 77
|
| 27 |
+
},
|
| 28 |
+
"split_views": {
|
| 29 |
+
"train": 1392,
|
| 30 |
+
"validation": 348,
|
| 31 |
+
"test": 308
|
| 32 |
+
},
|
| 33 |
+
"manifest_sha256": {
|
| 34 |
+
"train": "65af1c16cefde7e9cd411af055ccdee36c183382db18a4fd9a9d4ea82fb248ee",
|
| 35 |
+
"validation": "65ca208e5b7d59d398056c997751b12a18ce05c9361e37f4160f3b1420cbe52a",
|
| 36 |
+
"test": "c8f927c42ba2ccecd960536de8c7c7431c8fe674401e1fbe7d7613d18cab860d"
|
| 37 |
+
},
|
| 38 |
+
"grouping": "object IDs, shared Amazon item IDs, exact mesh/image/target hashes",
|
| 39 |
+
"limitations": "Near duplicates can remain. Cross-dataset overlap with ancestor training is not fully known; only recorded exact source hashes were excluded."
|
| 40 |
+
},
|
| 41 |
+
"baseline_validation": {
|
| 42 |
+
"occupancy": 0.5408538651877436,
|
| 43 |
+
"position": 0.0941663452948647,
|
| 44 |
+
"colour": 0.046021819156333646,
|
| 45 |
+
"scale": 0.002074153210627231,
|
| 46 |
+
"rotation": 1.2924113261747727e-05,
|
| 47 |
+
"opacity": 0.0002730773832950192,
|
| 48 |
+
"geometry": 0.0,
|
| 49 |
+
"loss": 0.7275456369950853,
|
| 50 |
+
"occupancy_iou": 0.2717048296217817
|
| 51 |
+
}
|
| 52 |
+
}
|