fotographer's picture
Fix model path models/voxel_scripted.pt
041aacc verified
Raw
History Blame Contribute Delete
3.88 kB
"""
Public VoxelMind inference — no Model.py / Affectors.py required.
Load the TorchScript artifact exported with scripts/export_scripted.py:
python scripts/export_scripted.py
"""
from __future__ import annotations
import argparse
from pathlib import Path
from typing import Sequence
import cv2
import numpy as np
import torch
import torch.nn.functional as F
TARGET_FPS = 22
TARGET_SIZE = (64, 64)
CLASS_NAMES: list[str] = [
"s",
"d",
"idle",
"mouse_up",
"jump",
"mouse_right",
"mouse_down",
"w",
"a",
"mouse_left",
"drop",
]
def frame_to_gray(frame_bgr: np.ndarray, target_size: tuple[int, int] = TARGET_SIZE) -> np.ndarray:
resized = cv2.resize(frame_bgr, target_size, interpolation=cv2.INTER_AREA)
return cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
def read_video_gray(path: str | Path, target_size: tuple[int, int] = TARGET_SIZE) -> list[np.ndarray]:
cap = cv2.VideoCapture(str(path))
frames: list[np.ndarray] = []
while cap.isOpened():
ok, frame = cap.read()
if not ok:
break
frames.append(frame_to_gray(frame, target_size))
cap.release()
if not frames:
raise ValueError(f"no frames in video: {path}")
return frames
def crop_temporal(frames: Sequence[np.ndarray], target_fps: int = TARGET_FPS) -> list[np.ndarray]:
n = len(frames)
if n >= target_fps:
start = (n - target_fps) // 2
return list(frames[start : start + target_fps])
padded = list(frames)
while len(padded) < target_fps:
padded.append(frames[-1])
return padded[:target_fps]
def frames_to_tensor(
frames_gray: Sequence[np.ndarray],
target_fps: int = TARGET_FPS,
target_size: tuple[int, int] = TARGET_SIZE,
) -> torch.Tensor:
video = torch.stack([torch.from_numpy(f) for f in frames_gray]).float() / 255.0
video = video.unsqueeze(0).unsqueeze(0)
video = F.interpolate(
video,
size=(len(frames_gray), target_size[0], target_size[1]),
mode="trilinear",
align_corners=False,
)
return video.squeeze(0)
def load_model(path: str | Path, device: str | torch.device = "cpu") -> torch.jit.ScriptModule:
model = torch.jit.load(str(path), map_location=device)
model.eval()
return model
def predict_tensor(
model: torch.jit.ScriptModule,
clip: torch.Tensor,
device: str | torch.device = "cpu",
topk: int = 3,
) -> list[tuple[str, float]]:
"""clip: [1, 22, 64, 64] or [1, 1, 22, 64, 64]"""
if clip.dim() == 4:
clip = clip.unsqueeze(0)
clip = clip.to(device)
with torch.inference_mode():
logits = model(clip)
probs = torch.softmax(logits, dim=-1).squeeze(0)
k = min(topk, probs.numel())
values, indices = probs.topk(k)
return [(CLASS_NAMES[i], float(v)) for v, i in zip(values, indices)]
def predict_video(
model: torch.jit.ScriptModule,
video_path: str | Path,
device: str | torch.device = "cpu",
topk: int = 3,
) -> list[tuple[str, float]]:
frames = crop_temporal(read_video_gray(video_path))
clip = frames_to_tensor(frames)
return predict_tensor(model, clip, device=device, topk=topk)
def main() -> None:
p = argparse.ArgumentParser(description="VoxelMind inference (TorchScript, no source model code)")
p.add_argument("--model", default="models/voxel_scripted.pt", help="TorchScript artifact")
p.add_argument("--video", required=True, help="Input .mp4 clip")
p.add_argument("--device", default="cpu", help="cpu | cuda")
p.add_argument("--topk", type=int, default=3)
args = p.parse_args()
model = load_model(args.model, args.device)
preds = predict_video(model, args.video, device=args.device, topk=args.topk)
for name, prob in preds:
print(f"{name:12s} {prob * 100:5.1f}%")
if __name__ == "__main__":
main()