File size: 4,691 Bytes
82542be
 
 
 
 
 
 
 
 
 
 
 
 
fd9a8c1
 
82542be
 
fd9a8c1
 
 
 
 
 
 
 
82542be
fd9a8c1
82542be
 
 
 
 
 
 
 
 
fd9a8c1
 
82542be
fd9a8c1
82542be
 
 
 
fd9a8c1
82542be
fd9a8c1
 
82542be
d42d85e
 
 
 
 
 
82542be
fd9a8c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4ff935e
3dc1399
fd9a8c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d42d85e
 
 
fd9a8c1
 
 
82542be
 
 
fd9a8c1
82542be
d42d85e
 
 
82542be
fd9a8c1
 
82542be
fd9a8c1
82542be
fd9a8c1
82542be
fd9a8c1
 
82542be
 
 
fd9a8c1
82542be
fd9a8c1
82542be
 
 
 
 
d42d85e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import os
from typing import Tuple

import gradio as gr
import numpy as np
import torch
import cv2

from moge.model.v2 import MoGeModel

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"


# ---------- Model setup ----------

@torch.no_grad()
def load_model() -> MoGeModel:
    """
    Load the mesh-capable MoGe model.

    NOTE:
    - If there is a dedicated mesh checkpoint (e.g. "Ruicheng/moge-2-vitl-mesh"),
      use that ID here.
    - If not, keep the normal one and use the mesh reconstruction API on it.
    """
    print(f"Loading MoGe model on device: {DEVICE}")
    # If there is a mesh-specific checkpoint, change this string accordingly.
    model = MoGeModel.from_pretrained("Ruicheng/moge-2-vitl-normal")
    model = model.to(DEVICE)
    model.eval()
    return model


MODEL = load_model()


# ---------- Helper: run MoGe mesh reconstruction ----------

@torch.no_grad()
def run_moge_mesh(image: np.ndarray) -> bytes:
    """
    image: HxWx3 RGB uint8 numpy array.

    Returns:
        glb_bytes: binary GLB data with texture baked, resolution ~256.
    """

    # Convert to float [0,1], CHW, batch
    img = image.astype(np.float32) / 255.0
    tensor = (
        torch.from_numpy(img)
        .permute(2, 0, 1)
        .unsqueeze(0)
        .to(DEVICE)  # (1,3,H,W)
    )

    # ---- IMPORTANT PART: call the mesh reconstruction API ----
    #
    # You need to adjust THIS CALL to match the actual MoGe code.
    # Look for something like:
    #   - MODEL.reconstruct_mesh(...)
    #   - MODEL.mesh_reconstruct(...)
    #   - MODEL.infer_mesh(...)
    #
    # And for arguments, look for:
    #   - mesh_resolution / grid_resolution
    #   - texture_size / tex_size
    #   - enable_texture / with_texture
    #
    # Below is a TEMPLATE that you should modify once you've checked the repo.

    # TEMPLATE call – this will almost certainly need renaming:
    result = MODEL.reconstruct_mesh(
        tensor,
        mesh_resolution=256,     # 256^3 grid or equivalent
        texture_size=256,        # 256x256 texture
        enable_texture=True,     # or with_texture=True, etc.
    )

    # ---- Inspect result structure (one-time debugging) ----
    # While debugging, you can keep these prints to see keys in Space logs:
    print("MoGe mesh result keys:", list(result.keys()))

    # Common patterns:
    #  1) result["glb"] -> raw GLB bytes
    #  2) result["mesh"] -> mesh object (trimesh / internal) with export method

    # Case 1: GLB bytes directly
    if "glb" in result:
        glb_bytes = result["glb"]
        if isinstance(glb_bytes, str):
            glb_bytes = glb_bytes.encode("utf-8")
        return glb_bytes

    # Case 2: mesh object with export method
    if "mesh" in result:
        mesh = result["mesh"]
        # If MoGe mesh exposes something like `to_glb(texture=..., texture_size=256)`:
        if hasattr(mesh, "to_glb"):
            tex = result.get("texture", None)
            if tex is not None:
                glb_bytes = mesh.to_glb(texture=tex, texture_size=256)
            else:
                glb_bytes = mesh.to_glb(texture_size=256)
            if isinstance(glb_bytes, str):
                glb_bytes = glb_bytes.encode("utf-8")
            return glb_bytes

        # Or if it expects file export:
        if hasattr(mesh, "export"):
            tmp_path = "output.glb"
            tex = result.get("texture", None)
            if tex is not None:
                # This is pseudocode – adapt to the actual mesh.export signature.
                mesh.export(tmp_path, texture=tex, texture_size=256)
            else:
                mesh.export(tmp_path)
            with open(tmp_path, "rb") as f:
                return f.read()

    raise RuntimeError(
        f"Unsupported MoGe mesh result structure: keys={list(result.keys())}"
    )


# ---------- Gradio inference function ----------

def infer_and_export_glb(image: np.ndarray):
    if image is None:
        raise gr.Error("Please upload an image.")

    glb_bytes = run_moge_mesh(image)

    glb_path = "output.glb"
    with open(glb_path, "wb") as f:
        f.write(glb_bytes)

    return glb_path


# ---------- Gradio app ----------

title = "MoGe 3D Reconstruction β†’ Textured GLB (256)"
description = (
    "Upload an image. MoGe reconstructs a textured 3D mesh and exports it as a GLB "
    "with a ~256x256 texture."
)

demo = gr.Interface(
    fn=infer_and_export_glb,
    inputs=gr.Image(type="numpy", label="Input image"),
    outputs=gr.File(label="Download GLB (textured mesh)"),
    title=title,
    description=description,
)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)