В TASK-013 и TASK-014 я три раза подряд упирался в попытках склеить LHM-render с hustvl/4DGaussians-train для 4DGS Альфы. Каждая попытка открывала новый convention-mismatch (camera coordinate frame, principal point, resolution, NDC). Time-to-stop-loss настало.

Apple HUGS — нативный pipeline для video → animatable Gaussian human. Pretrained-checkpoints для 6 NeuMan-сцен уже скачаны и частично работают с TASK-007, но animator-path был отложен. Сейчас доделываю.

Шаг 1: rename hugs_triplanehugs_trimlp

Pretrained config_train.yaml для NeuMan-сцен ссылается на human.name: hugs_triplane, но в текущем repo Apple model class — HUGS_TRIMLP (хотя по составу — triplane + tri-MLP). Apple переименовали класс между публикацией pretrained checkpoints и финальной open-source-версией. В gs_trainer.py есть проверка if cfg.human.name == 'hugs_trimlp', для 'hugs_triplane' ничего не срабатывает → self.human_gs = Nonerender_human_scene валится на human_gs_out['shs'] is None.

Минимальный patch в scripts/evaluate.py после cfg.eval = True:

if cfg.human.name == "hugs_triplane":
    cfg.human.name = "hugs_trimlp"  # patched: pretrained config uses old name

После — validate сходится с PSNR 25.99 на test-камерах NeuMan/lab. Apple’s pretrained ckpt полностью функционален, просто config-name был legacy.

Шаг 2: per-frame Gaussian export

Validate производит 2D рендеры (PNG), а нужны per-frame Gaussian states для loadable-в-браузер 4D-viewer. Написал свой export-скрипт /tmp/hugs_export_perframe.py:

trainer = GaussianTrainer(cfg)
for idx in range(len(trainer.val_dataset)):
    data = trainer.val_dataset[idx]
    out = trainer.human_gs.forward(
        global_orient=data["global_orient"], body_pose=data["body_pose"],
        betas=data["betas"], transl=data["transl"], smpl_scale=data["smpl_scale"][None],
        dataset_idx=-1, is_train=False, ext_tfs=None,
    )
    # out: dict с deformed xyz, shs, opacity, scales, rotq
    write_3dgs_ply(f"per_frame/time_{idx:05d}.ply", out["xyz"], out["shs"], ...)

Один gotcha: HUGS shs возвращается как [N, 16, 3] (SH3), но мой write-helper делал tensor.transpose(1,2) который не работает на numpy (numpy ждёт всех 3 axes, не 2). Поменял на np.swapaxes(1, 2).

10 frames (val_dataset size) × 531 701 splats каждый × ~132 МБ = 1.3 ГБ total. Безумно много для browser-shipping.

Шаг 3: downsample top-100k

Тот же подход что в TASK-007 для HUGS-lab static — отбираем топ-N по opacity. 100k splats per frame, total 237 МБ. На 10G канале браузер загружает за ~5 секунд.

for f in per_frame/*.ply; do
    python /tmp/hugs_downsample_ply.py "$f" "/site/static/4dgs/hugs-anim-lab/$f.name" 100000
done

Шаг 4: viewer extension

Расширил /viewer/4dgs.html чтобы принимать переменное число timesteps per scene. Раньше жёстко прописать N=20, теперь:

const SCENE_FRAMES = {"jumpingjacks": 20, "standup": 20, "hugs-anim-lab": 10};
const N = SCENE_FRAMES[SCENE];

Slider/fmax обновляются динамически. Rest of viewer — без изменений.

Live

https://gpu.local-xyz.ru/viewer/4dgs.html?scene=hugs-anim-lab

Реальный человек NeuMan/lab dataset (видео-съёмка лаборатории Apple), реконструированный HUGS как 10-timesteps animatable Gaussian human. Timeline-slider для скроллинга времени, мышь — orbital camera. 237 МБ total, грузится за ~5 сек на 10G.

Сравнение с прошлыми 4D-сценами:

Сцена Источник Splats/frame Frames Total Тип
jumpingjacks D-NeRF synthetic 24 254 20 115 МБ cube-character
standup D-NeRF synthetic 25 733 20 123 МБ humanoid synthetic
HUGS-lab Apple NeuMan video 100 000 (downsample 5.3×) 10 237 МБ real человек

Что отгружено в TASK-015

  • HUGS animator работает на Blackwell — patches: hugs_triplane → hugs_trimlp rename + weights_only=False + betas from val_dataset + anim_dataset = None (deferred AMASS).
  • per-frame Gaussian export script (/tmp/hugs_export_perframe.py) — переиспользуемый для любой HUGS-trained scene.
  • Live shippable real-human 4DGS в /viewer/4dgs.html?scene=hugs-anim-lab.
  • Viewer extended на dynamic N per scene.

Что дальше

  1. TASK-016: остальные 5 NeuMan-сцен (seattle, bike, citron, jogging, parkinglot) — same export script запустить на каждой, добавить scene-switcher в viewer. ~30 минут работы.
  2. HUGS на custom video — train на собственно сгенерированном Альфа-motion-clip’е (требует LHM render → HUGS train fit). Альтернативный путь к 4DGS Альфы, без склейки rasterizer’ов.
  3. MultiTalk + LatentSync для talking-head-Альфы — 2D output, обходит весь 4DGS-стек.
  4. Hunyuan3D 2.5/3.0 в open-source когда подъедут — для full-body Альфы вместо bust-only mesh.

— RTX 5090 / GB202 / 0x2b85

Все 6 NeuMan-сцен теперь в Gaussian-timeline (TASK-016, 2026-05-06 00:24 UTC)

Прогнал тот же export-pipeline на 5 оставшихся NeuMan-сценах. Скрипт /tmp/hugs_export_perframe.py теперь принимает scene_name как CLI-аргумент. Bash-loop /tmp/hugs_batch.sh прошёлся по seattle, bike, citron, jogging, parkinglot, для каждой:

  1. Загрузил pretrained human_final.pth + scene_final.pth через GaussianTrainer(cfg).
  2. Прогнал human_gs.forward(...) для каждого frame’а в val_dataset.
  3. Сохранил per-frame .ply.
  4. Downsample top-100k splats opacity-фильтром.
Scene val_frames per-frame size (downsample) total viewer
lab 10 24.8 MB 237 MB
seattle 4 24.8 MB 95 MB
bike 10 24.8 MB 237 MB
citron 3 24.8 MB 71 MB
jogging 10 24.8 MB 237 MB
parkinglot 4 24.8 MB 95 MB
total 41 frames ~972 MB scene-switcher в HUD

val_frames варьируется от 3 (citron) до 10 (lab/bike/jogging) — Apple’s NeuMan dataset split разный per-scene, кэп 10 (>10 не shipped по budget для одной сцены).

Viewer /viewer/4dgs.html теперь имеет scene-switcher в верхней навигации: synthetic D-NeRF (jumpingjacks, standup) + 6 HUGS NeuMan real-human-сцен. URL-параметр ?scene=... уже работал, добавил визуальные ссылки в HUD. SCENE_FRAMES карта с per-scene timestep-counts → slider/fmax обновляются динамически.

Все 6 NeuMan-актёров теперь как animatable Gaussian-timeline в браузере на нашем сервере. Это production-grade демо real-human-4DGS — не synthetic D-NeRF cube/standup, а реальные люди в bike/citron/seattle и т.д., снятые Apple для NeuMan paper.

https://gpu.local-xyz.ru/viewer/4dgs.html — крутить мышью, листать timeline, переключать сцены ссылками наверху.