File size: 5,355 Bytes
c9780e6
e83d360
 
 
 
a083e2f
e83d360
 
58b77b1
e83d360
36dff58
56b5c79
 
 
1e1ee77
 
 
56b5c79
 
 
 
 
 
 
 
 
 
8e9f8c2
 
edca60d
 
 
 
8d31456
edca60d
 
 
 
e83d360
 
e43fc9e
a083e2f
e43fc9e
 
 
edca60d
36ae991
c9780e6
81f91cc
 
0119d95
 
81f91cc
 
 
 
 
 
0119d95
9c917c1
edca60d
9c917c1
c9780e6
 
9c917c1
 
 
 
 
a083e2f
9c917c1
a083e2f
9c917c1
e43fc9e
9c917c1
e83d360
 
 
a083e2f
36ae991
9c917c1
a2b5338
 
e43fc9e
 
 
 
 
edca60d
c9780e6
 
 
 
 
 
 
 
 
 
a2b5338
dc42813
e83d360
0119d95
 
 
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
import json
import gradio as gr
import spaces, torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from PIL import Image
from typing import Literal


# @spaces.GPU(duration=30)
def load_model():
    # moondream2
    # return AutoModelForCausalLM.from_pretrained(
    #     "vikhyatk/moondream2",
    #     revision="2025-04-14",
    #     trust_remote_code=True,
    #     device_map={"": "cuda"},
    # )

    # moondream3-preview
    moondream = AutoModelForCausalLM.from_pretrained(
        "moondream/moondream3-preview",
        trust_remote_code=True,
        dtype=torch.bfloat16,
        device_map={"": "cuda"},
    )
    moondream.compile()
    return moondream


_MODEL_ZOO = {
    "moondream3-preview": load_model(),  # calling spaces.GPU decorated functions outside ZeroGPU scope will cause a PickingError
    "moondream2": AutoModelForCausalLM.from_pretrained(
        "vikhyatk/moondream2",
        revision="2025-06-21",
        trust_remote_code=True,
        device_map={"": "cuda"},
    ),
}


@spaces.GPU(duration=30)
def detect(
    im: Image.Image,
    object_name: str,
    mode: Literal["point", "object_detection", "query"],
    model_name: Literal["moondream2", "moondream3-preview"] = "moondream3-preview",
    reasoning: bool = False,
    settings: dict = {"temperature": 0.0, "top_p": 0.95, "max_tokens": 512},
) -> list[dict] | dict:
    """Open-vocabulary object localization and visual question answering with the Moondream vision-language model. The shape of the returned JSON depends on the "mode" argument. In "point" mode it returns a JSON list with one entry per located instance of "object_name"; each entry is an object {"x": float, "y": float} giving that instance's center as coordinates NORMALIZED to 0.0-1.0 of the input image (x is fraction of width from the left edge, y is fraction of height from the top edge). In "object_detection" mode it returns a JSON list with one entry per located instance; each entry is a bounding box {"x_min": float, "y_min": float, "x_max": float, "y_max": float} where all four values are NORMALIZED to 0.0-1.0 of the ORIGINAL input image ("x_min"/"y_min" is the top-left corner, "x_max"/"y_max" is the bottom-right corner) -- this is the albumentations "albumentations" bounding-box format (normalized [x_min, y_min, x_max, y_max]), documented at https://albumentations.ai/docs/3-basic-usage/bounding-boxes-augmentations/#bounding-box-formats -- values are NOT absolute pixels and the box is NOT [x, y, width, height]. In "query" mode it returns a JSON object {"answer": string} answering "object_name" as a natural-language question about the image.

    Args:
        im: The RGB image to run detection or answer a question about.
        object_name: In "point"/"object_detection" mode, the open-vocabulary name of the object to locate (e.g. "person", "red car"); in "query" mode, the natural-language question to ask about the image.
        mode: "point" returns normalized center points, "object_detection" returns normalized bounding boxes, "query" returns a text answer.
        model_name: Which Moondream checkpoint to run: "moondream3-preview" (default, more capable) or "moondream2".
        reasoning: Enable chain-of-thought reasoning; only applies to "query" mode on the moondream3-preview model and is ignored otherwise.
        settings: Inference parameters as a JSON object; for "query" mode use "temperature"/"top_p"/"max_tokens", for "point"/"object_detection" use "max_objects"; ignored for the moondream2 model.
    """

    model = _MODEL_ZOO[model_name]
    is_v2 = model_name == "moondream2"
    if isinstance(settings, str):
        settings = json.loads(settings)

    inf_params = {} if is_v2 else {"settings": settings}
    if not is_v2 and mode == "query":
        inf_params["reasoning"] = reasoning

    if mode == "point":
        return model.point(im, object_name, **inf_params)["points"]
    elif mode == "object_detection":
        return model.detect(im, object_name, **inf_params)["objects"]
    elif mode == "query":
        return model.query(im, object_name, **inf_params)


demo = gr.Interface(
    fn=detect,
    title="moondream-pointer",
    description="using [moondream3-preview](https://huggingface.co/moondream/moondream3-preview) and [moondream2](https://huggingface.co/vikhyatk/moondream2) for object grounding",
    inputs=[
        gr.Image(label="Input Image", type="pil"),
        gr.Textbox(
            label="Object / Question",
            info="object to detector (for points / object_detection) or question for a query",
        ),
        gr.Dropdown(label="Mode", choices=["point", "object_detection", "query"]),
        gr.Dropdown(label="Model Variant", choices=list(_MODEL_ZOO.keys())),
        gr.Checkbox(
            label="Reasoning",
            value=False,
            info="enable [chain-of-thought](https://huggingface.co/moondream/moondream3-preview#query) (query mode only)",
        ),
        gr.Textbox(
            label="Settings (JSON)",
            value='{"temperature": 0.0, "top_p": 0.95, "max_tokens": 512}',
            info="query: temperature / top_p / max_tokens Β· point & object_detection: max_objects",
        ),
    ],
    outputs=gr.JSON(label="Output JSON"),
)
demo.launch(
    mcp_server=True, app_kwargs={"docs_url": "/docs"}  # add FastAPI Swagger API Docs
)