Download src/image3d_student/model.py from Lucasgitguy-co/forma3d-tiny-3: direct link, hf CLI and curl.
- Browser
- Download file 3.81 kB
-
https://huggingface.co/Lucasgitguy-co/forma3d-tiny-3/resolve/main/src/image3d_student/model.py
- Command line
-
hf download hf://Lucasgitguy-co/forma3d-tiny-3/src/image3d_student/model.py
-
curl -L -o model.py https://huggingface.co/Lucasgitguy-co/forma3d-tiny-3/resolve/main/src/image3d_student/model.py
3.81 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2026 Lucasgitguy-co | |
| import math | |
| import torch | |
| from torch import nn | |
| from torch.nn import functional as F | |
| from .data import grid_centers | |
| class Student(nn.Module): | |
| """Small image-conditioned coordinate MLP; fixed cells avoid unordered point regression. | |
| 15 Gaussian outputs + 2 auxiliary geometry logits. This is a coarse baseline, | |
| not an architecture expected to reproduce million-splat teacher detail. | |
| """ | |
| def __init__(self, grid=16, width=128): | |
| super().__init__() | |
| if grid not in (8, 16, 32) or width < 16: | |
| raise ValueError("Unsupported grid or width") | |
| self.config = {"grid": grid, "width": width} | |
| self.grid = grid | |
| blocks, channels = [], 3 | |
| for out in (24, 48, 96, 128): | |
| blocks.extend([nn.Conv2d(channels, out, 3, stride=2, padding=1), nn.GroupNorm(8, out), nn.SiLU()]) | |
| channels = out | |
| self.encoder = nn.Sequential(*blocks, nn.AdaptiveAvgPool2d(4), nn.Flatten(), | |
| nn.Linear(128*4*4, width), nn.SiLU()) | |
| centers = torch.from_numpy(grid_centers(grid)) | |
| freq = 2**torch.arange(4, dtype=torch.float32) * math.pi * 2 | |
| angles = centers[..., None] * freq | |
| self.register_buffer("centers", centers) | |
| self.register_buffer("coords", torch.cat([centers, angles.sin().flatten(1), angles.cos().flatten(1)], dim=1)) | |
| self.query = nn.Sequential(nn.Linear(27, width), nn.SiLU()) | |
| self.decoder = nn.Sequential(nn.Linear(width, width), nn.SiLU(), nn.Linear(width, width), nn.SiLU(), nn.Linear(width, 17)) | |
| with torch.no_grad(): | |
| self.decoder[-1].bias.zero_() | |
| self.decoder[-1].bias[0] = -2 | |
| def forward(self, images): | |
| features = self.encoder(images) | |
| raw = self.decoder(features[:, None, :] + self.query(self.coords)[None]) | |
| quat = raw[..., 10:14] + raw.new_tensor([1, 0, 0, 0]) | |
| return {"occ": raw[..., :1], "xyz": self.centers[None] + raw[..., 1:4].tanh() / (2*self.grid), | |
| "rgb": raw[..., 4:7].sigmoid(), | |
| "scale": (raw[..., 7:10].clamp(-8, 5) - math.log(self.grid*3)).exp(), | |
| "quat": F.normalize(quat, dim=-1, eps=1e-8), "opacity": raw[..., 14:15].sigmoid(), | |
| "geometry": raw[..., 15:17]} | |
| def balanced_bce(logits, target): | |
| error = F.binary_cross_entropy_with_logits(logits, target, reduction="none") | |
| positive = (error * target).sum() / target.sum().clamp_min(1) | |
| negative = (error * (1-target)).sum() / (1-target).sum().clamp_min(1) | |
| return (positive + negative) / 2 | |
| def distillation_loss(pred, target, grid): | |
| mask = target["occ"] | |
| def masked(value): | |
| return (value * mask).sum() / (mask.sum().clamp_min(1) * value.shape[-1]) | |
| terms = {"occupancy": balanced_bce(pred["occ"], mask), | |
| "position": masked(((pred["xyz"] - target["xyz"]) * grid).square()), | |
| "colour": masked((pred["rgb"] - target["rgb"].clamp(0, 1)).square()), | |
| "scale": masked((pred["scale"].log() - target["scale"].clamp_min(1e-8).log()).square()), | |
| "rotation": masked(1 - (pred["quat"] * target["quat"]).sum(-1, keepdim=True).square().clamp(0, 1)), | |
| "opacity": masked((pred["opacity"] - target["opacity"]).square())} | |
| geometry = pred["geometry"].sum() * 0 | |
| for i in range(2): | |
| available = target["geometry_valid"][:, i] > 0 | |
| if available.any(): | |
| geometry = geometry + balanced_bce(pred["geometry"][available, :, i:i+1], target["geometry"][available, :, i:i+1]) | |
| terms["geometry"] = geometry | |
| total = terms["occupancy"] + terms["position"] + 2*terms["colour"] + .1*terms["scale"] + .1*terms["rotation"] + terms["opacity"] + .25*geometry | |
| return total, terms | |