Instructions to use litert-community/Idol with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/Idol with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Download python/render_views.py from litert-community/Idol: direct link, hf CLI and curl.
- Browser
- Download file 6.19 kB
-
https://huggingface.co/litert-community/Idol/resolve/main/python/render_views.py
- Command line
-
hf download hf://litert-community/Idol/python/render_views.py
-
curl -L -o render_views.py https://huggingface.co/litert-community/Idol/resolve/main/python/render_views.py
6.19 kB
| """CONTRACT.md line (b) renderer: a 3DGS .ply -> 4 fixed views, 512², on the Mac CPU. | |
| Projection, covariance and tile intersection are gsplat 1.5.3's pure-PyTorch functions (nerfstudio-project/gsplat, | |
| Apache-2.0, `gsplat/cuda/_torch_impl.py`, loaded from the installed wheel, unmodified). Compositing follows gsplat's | |
| `accumulate`: alpha = min(0.999, opacity * exp(-sigma)), weight = alpha * prod(1 - alpha in front), over every | |
| Gaussian whose 3.33-sigma box touches the 16x16 tile, front to back by camera depth. | |
| python render_views.py --ply A.ply [--ply B.ply] --out DIR [--psnr] (with two .ply: PSNR of B vs A + side-by-side) | |
| """ | |
| import argparse | |
| import glob | |
| import importlib.util | |
| import json | |
| import math | |
| import os | |
| import sys | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, HERE) | |
| import idol_glue as GL # noqa: E402 | |
| _p = glob.glob(os.path.join(os.path.dirname(torch.__file__), '..', 'gsplat', 'cuda', '_torch_impl.py'))[0] | |
| _spec = importlib.util.spec_from_file_location('gsplat_torch_impl', _p) | |
| GS = importlib.util.module_from_spec(_spec) | |
| _spec.loader.exec_module(GS) | |
| C0 = 0.28209479177387814 | |
| SIZE, TILE = 512, 16 | |
| AZIMUTHS = (0.0, 90.0, 180.0, 270.0) | |
| ELEVATION, DISTANCE, FOV_Y = 20.0, 2.2, 40.0 | |
| def cameras(): | |
| viewmats, Ks = [], [] | |
| # idol's PLY is y-down, as 3DGS viewers read it (the object's top is at -y): image up = -y, cameras above at -y | |
| up = np.array([0.0, -1.0, 0.0]) | |
| f = (SIZE / 2) / math.tan(math.radians(FOV_Y) / 2) | |
| for az in AZIMUTHS: | |
| a, e = math.radians(az), math.radians(ELEVATION) | |
| c = DISTANCE * np.array([math.cos(e) * math.sin(a), -math.sin(e), math.cos(e) * math.cos(a)]) | |
| z = -c / np.linalg.norm(c) # forward (OpenCV camera: x right, y down, z forward) | |
| x = np.cross(z, up); x /= np.linalg.norm(x) | |
| y = np.cross(z, x) | |
| R = np.stack([x, y, z]) | |
| m = np.eye(4); m[:3, :3] = R; m[:3, 3] = -R @ c | |
| viewmats.append(m) | |
| Ks.append(np.array([[f, 0, SIZE / 2], [0, f, SIZE / 2], [0, 0, 1.0]])) | |
| return torch.tensor(np.stack(viewmats), dtype=torch.float32), torch.tensor(np.stack(Ks), dtype=torch.float32) | |
| def load(path): | |
| names, rows = GL.read_ply(path) | |
| col = {n: i for i, n in enumerate(names)} | |
| t = torch.from_numpy(np.ascontiguousarray(rows)) | |
| means = t[:, [col['x'], col['y'], col['z']]] | |
| quats = torch.nn.functional.normalize(t[:, [col['rot_0'], col['rot_1'], col['rot_2'], col['rot_3']]], dim=-1) | |
| scales = torch.exp(t[:, [col['scale_0'], col['scale_1'], col['scale_2']]]) | |
| opac = torch.sigmoid(t[:, col['opacity']]) | |
| colors = (0.5 + C0 * t[:, [col['f_dc_0'], col['f_dc_1'], col['f_dc_2']]]).clamp_min(0.0) | |
| return means, quats, scales, opac, colors | |
| def render(path): | |
| means, quats, scales, opac, colors = load(path) | |
| viewmats, Ks = cameras() | |
| covars, _ = GS._quat_scale_to_covar_preci(quats, scales, compute_covar=True, compute_preci=False) | |
| radii, means2d, depths, conics, _ = GS._fully_fused_projection(means, covars, viewmats, Ks, SIZE, SIZE, | |
| eps2d=0.3, near_plane=0.01, far_plane=1e10) | |
| tw = th = SIZE // TILE | |
| _, isect_ids, flatten_ids = GS._isect_tiles(means2d, radii, depths, TILE, tw, th, sort=True) | |
| offsets = GS._isect_offset_encode(isect_ids, len(AZIMUTHS), tw, th).flatten().tolist() + [len(flatten_ids)] | |
| N = means.shape[0] | |
| flat = flatten_ids.long() | |
| out = torch.zeros(len(AZIMUTHS), SIZE, SIZE, 3) | |
| py, px = torch.meshgrid(torch.arange(TILE, dtype=torch.float32), torch.arange(TILE, dtype=torch.float32), | |
| indexing='ij') | |
| for k in range(len(offsets) - 1): | |
| s, e = offsets[k], offsets[k + 1] | |
| if e <= s: | |
| continue | |
| cam, rem = divmod(k, tw * th) | |
| ty, tx = divmod(rem, tw) | |
| gid = flat[s:e] - cam * N | |
| m2 = means2d[cam, gid] | |
| cn = conics[cam, gid] | |
| dx = (px.flatten() + tx * TILE + 0.5)[None, :] - m2[:, 0:1] | |
| dy = (py.flatten() + ty * TILE + 0.5)[None, :] - m2[:, 1:2] | |
| sigma = 0.5 * (cn[:, 0:1] * dx * dx + cn[:, 2:3] * dy * dy) + cn[:, 1:2] * dx * dy | |
| alpha = torch.clamp_max(opac[gid][:, None] * torch.exp(-sigma), 0.999) | |
| trans = torch.cumprod(torch.cat([torch.ones(1, alpha.shape[1]), 1 - alpha[:-1]], dim=0), dim=0) | |
| w = alpha * trans | |
| rgb = (w.t() @ colors[gid]).reshape(TILE, TILE, 3) | |
| out[cam, ty * TILE:(ty + 1) * TILE, tx * TILE:(tx + 1) * TILE] = rgb | |
| return out.clamp(0, 1) | |
| def psnr(a, b): | |
| mse = float(((a.double() - b.double()) ** 2).mean()) | |
| return 10 * math.log10(1.0 / max(mse, 1e-12)), mse | |
| def to_u8(img): | |
| return (img.numpy() * 255.0 + 0.5).clip(0, 255).astype(np.uint8) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--ply', action='append', required=True, help='first = reference (author), second = ours') | |
| ap.add_argument('--out', required=True) | |
| ap.add_argument('--tag', default='') | |
| a = ap.parse_args() | |
| os.makedirs(a.out, exist_ok=True) | |
| views = [render(p) for p in a.ply] | |
| for i, v in enumerate(views): | |
| for j, az in enumerate(AZIMUTHS): | |
| Image.fromarray(to_u8(v[j])).save(os.path.join(a.out, f'view{i}_az{int(az):03d}.png')) | |
| res = {'ply': a.ply, 'cameras': {'azimuth_deg': AZIMUTHS, 'elevation_deg': ELEVATION, 'distance': DISTANCE, | |
| 'fov_y_deg': FOV_Y, 'size': SIZE}, 'renderer': 'gsplat 1.5.3 _torch_impl'} | |
| if len(views) == 2: | |
| p_all, mse = psnr(views[0], views[1]) | |
| res.update(psnr_db=p_all, mse=mse, psnr_per_view_db=[psnr(views[0][j], views[1][j])[0] | |
| for j in range(len(AZIMUTHS))]) | |
| grid = np.concatenate([np.concatenate([to_u8(v[j]) for j in range(len(AZIMUTHS))], axis=1) for v in views]) | |
| Image.fromarray(grid).save(os.path.join(a.out, f'b_{a.tag or "pair"}.png')) | |
| json.dump(res, open(os.path.join(a.out, 'render.json'), 'w'), indent=1) | |
| print(json.dumps(res), flush=True) | |
| if __name__ == '__main__': | |
| main() | |