garmentiq.grounding

Text-to-region grounding.

Grounding models turn a natural-language phrase into bounding boxes. This is what gives SAM 1 and SAM 2 text-prompted segmentation, since neither contains a text encoder. The boxes are model-agnostic, so this module is deliberately independent of the segmentation package.

The model classes are re-exported from transformers and documented upstream, so they are not repeated here. GroundingDinoForObjectDetection, GroundingDinoConfig, and GroundingDinoProcessor can all be imported from this module directly.

 1# garmentiq/grounding/__init__.py
 2"""Text-to-region grounding.
 3
 4Grounding models turn a natural-language phrase into bounding boxes. This is what
 5gives SAM 1 and SAM 2 text-prompted segmentation, since neither contains a text
 6encoder. The boxes are model-agnostic, so this module is deliberately independent
 7of the segmentation package.
 8
 9The model classes are re-exported from `transformers` and documented upstream, so they
10are not repeated here. `GroundingDinoForObjectDetection`, `GroundingDinoConfig`, and
11`GroundingDinoProcessor` can all be imported from this module directly.
12"""
13from .grounding_dino import (
14    load_grounding_config,
15    load_grounding_processor,
16    load_grounding_model,
17    ground_text_to_boxes,
18)
19
20_LAZY = {
21    "GroundingDinoForObjectDetection",
22    "GroundingDinoConfig",
23    "GroundingDinoProcessor",
24}
25
26
27def __getattr__(name):
28    """Defer transformers imports until a grounding class is actually requested."""
29    if name in _LAZY:
30        from . import grounding_dino
31
32        return getattr(grounding_dino, name)
33    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
34
35
36__all__ = [
37    "load_grounding_config",
38    "load_grounding_processor",
39    "load_grounding_model",
40    "ground_text_to_boxes",
41]
42
43
44def __dir__():
45    """List the lazily resolved model classes alongside the eager exports."""
46    return sorted(set(__all__) | _LAZY)
def load_grounding_config(model_dir: str):
57def load_grounding_config(model_dir: str):
58    """
59    Reads and loads a local Grounding DINO configuration for offline use.
60
61    Grounding DINO turns a natural-language phrase into bounding boxes, which SAM 1 and
62    SAM 2 accept natively as prompts. Its configuration and tokenizer files are not
63    bundled with GarmentIQ because they belong to a separate upstream model, so download
64    the model once and point `model_dir` at the resulting directory.
65
66    Args:
67        model_dir (str): Path to a local directory containing the Grounding DINO
68                         `config.json`, e.g. a download of `IDEA-Research/grounding-dino-tiny`.
69
70    Raises:
71        FileNotFoundError: If `model_dir` is not a directory or does not contain `config.json`.
72        ImportError: If the installed transformers release does not provide Grounding DINO.
73
74    Returns:
75        GroundingDinoConfig: The loaded configuration object, ready to be passed into
76                             `GroundingDinoForObjectDetection`.
77    """
78    if not os.path.isdir(model_dir):
79        raise FileNotFoundError(
80            f"Provided model_dir '{model_dir}' is not an existing directory."
81        )
82
83    config_path = os.path.join(model_dir, "config.json")
84    if not os.path.exists(config_path):
85        raise FileNotFoundError(f"Offline config missing at {config_path}.")
86
87    with open(config_path, "r") as f:
88        config_dict = json.load(f)
89
90    return _resolve("GroundingDinoConfig").from_dict(config_dict)

Reads and loads a local Grounding DINO configuration for offline use.

Grounding DINO turns a natural-language phrase into bounding boxes, which SAM 1 and SAM 2 accept natively as prompts. Its configuration and tokenizer files are not bundled with GarmentIQ because they belong to a separate upstream model, so download the model once and point model_dir at the resulting directory.

Arguments:
  • model_dir (str): Path to a local directory containing the Grounding DINO config.json, e.g. a download of IDEA-Research/grounding-dino-tiny.
Raises:
  • FileNotFoundError: If model_dir is not a directory or does not contain config.json.
  • ImportError: If the installed transformers release does not provide Grounding DINO.
Returns:

GroundingDinoConfig: The loaded configuration object, ready to be passed into GroundingDinoForObjectDetection.

def load_grounding_processor(model_dir: str):
 93def load_grounding_processor(model_dir: str):
 94    """
 95    Loads a local Grounding DINO processor for offline use.
 96
 97    The processor pairs an image processor with a text tokenizer, so the whole local
 98    directory is required rather than a single JSON file.
 99
100    Args:
101        model_dir (str): Path to a local directory containing the Grounding DINO processor
102                         and tokenizer files.
103
104    Raises:
105        FileNotFoundError: If `model_dir` is not an existing directory.
106        ImportError: If the installed transformers release does not provide Grounding DINO.
107
108    Returns:
109        GroundingDinoProcessor: The instantiated processor ready for image and text inputs.
110    """
111    if not os.path.isdir(model_dir):
112        raise FileNotFoundError(
113            f"Provided model_dir '{model_dir}' is not an existing directory."
114        )
115
116    return _resolve("GroundingDinoProcessor").from_pretrained(model_dir)

Loads a local Grounding DINO processor for offline use.

The processor pairs an image processor with a text tokenizer, so the whole local directory is required rather than a single JSON file.

Arguments:
  • model_dir (str): Path to a local directory containing the Grounding DINO processor and tokenizer files.
Raises:
  • FileNotFoundError: If model_dir is not an existing directory.
  • ImportError: If the installed transformers release does not provide Grounding DINO.
Returns:

GroundingDinoProcessor: The instantiated processor ready for image and text inputs.

def load_grounding_model(model_dir: str, device: Union[str, torch.device] = 'cpu'):
119def load_grounding_model(
120    model_dir: str,
121    device: Union[str, torch.device] = "cpu",
122):
123    """
124    Loads a Grounding DINO model from a local directory, fully offline.
125
126    Grounding DINO ties several decoder heads to a shared set of weights, so its checkpoint
127    stores only one copy of them. Constructing the model from a config and calling
128    `load_state_dict` therefore leaves those tied tensors randomly initialised, which
129    silently degrades grounding quality. `from_pretrained` performs the tying correctly,
130    so it is used here rather than the generic loader used for other GarmentIQ models.
131
132    Args:
133        model_dir (str): Path to a local directory holding the Grounding DINO `config.json`,
134                         weights, and tokenizer files, e.g. a download of
135                         `IDEA-Research/grounding-dino-tiny`.
136        device (Union[str, torch.device], optional): The device to load the model onto, e.g.
137                                                     `"cpu"`, `"cuda"`, or `"mps"`.
138                                                     Default is `"cpu"`.
139
140    Raises:
141        FileNotFoundError: If `model_dir` is not an existing directory.
142        ValueError: If the requested `device` is invalid or unavailable on this machine.
143        ImportError: If the installed transformers release does not provide Grounding DINO.
144
145    Returns:
146        GroundingDinoForObjectDetection: The loaded model in evaluation mode, on `device`.
147    """
148    if not os.path.isdir(model_dir):
149        raise FileNotFoundError(
150            f"Provided model_dir '{model_dir}' is not an existing directory."
151        )
152
153    device = resolve_device(device)
154    model = _resolve("GroundingDinoForObjectDetection").from_pretrained(model_dir)
155    model = model.to(device)
156    model.eval()
157    return model

Loads a Grounding DINO model from a local directory, fully offline.

Grounding DINO ties several decoder heads to a shared set of weights, so its checkpoint stores only one copy of them. Constructing the model from a config and calling load_state_dict therefore leaves those tied tensors randomly initialised, which silently degrades grounding quality. from_pretrained performs the tying correctly, so it is used here rather than the generic loader used for other GarmentIQ models.

Arguments:
  • model_dir (str): Path to a local directory holding the Grounding DINO config.json, weights, and tokenizer files, e.g. a download of IDEA-Research/grounding-dino-tiny.
  • device (Union[str, torch.device], optional): The device to load the model onto, e.g. "cpu", "cuda", or "mps". Default is "cpu".
Raises:
  • FileNotFoundError: If model_dir is not an existing directory.
  • ValueError: If the requested device is invalid or unavailable on this machine.
  • ImportError: If the installed transformers release does not provide Grounding DINO.
Returns:

GroundingDinoForObjectDetection: The loaded model in evaluation mode, on device.

def ground_text_to_boxes( model, processor, image: PIL.Image.Image, text: str, box_threshold: float = 0.3, text_threshold: float = 0.3, max_boxes: Optional[int] = None, device: Union[str, torch.device] = 'cpu'):
160def ground_text_to_boxes(
161    model,
162    processor,
163    image: Image.Image,
164    text: str,
165    box_threshold: float = 0.3,
166    text_threshold: float = 0.3,
167    max_boxes: Optional[int] = None,
168    device: Union[str, torch.device] = "cpu",
169):
170    """
171    Converts a natural-language phrase into bounding boxes for an image.
172
173    This is the bridge that gives SAM 1 and SAM 2 text-prompted segmentation. Neither model
174    contains a text encoder, so the phrase is first grounded into boxes by Grounding DINO,
175    and those boxes are then used as ordinary geometric prompts for SAM.
176
177    Grounding DINO expects lowercase phrases terminated by a period; the text is normalised
178    to that convention automatically.
179
180    Args:
181        model (GroundingDinoForObjectDetection): The loaded Grounding DINO model.
182        processor (GroundingDinoProcessor): The matching processor with its tokenizer.
183        image (PIL.Image.Image): The image to ground the phrase against.
184        text (str): The natural-language prompt, e.g. `"a shirt"` or `"trousers"`.
185        box_threshold (float, optional): Minimum box confidence to keep a detection.
186                                         Default is 0.3.
187        text_threshold (float, optional): Minimum text-matching score to keep a detection.
188                                          Default is 0.3.
189        max_boxes (int, optional): If given, keep at most this many highest-scoring boxes.
190                                   Default is None, meaning keep all boxes above threshold.
191        device (Union[str, torch.device], optional): The device to run grounding on, e.g.
192                                                     `"cpu"`, `"cuda"`, or `"mps"`.
193                                                     Default is `"cpu"`.
194
195    Raises:
196        ValueError: If `text` is empty, if the requested `device` is unavailable, or if no
197                    region of the image matches the phrase at the given thresholds.
198
199    Returns:
200        list[list[float]]: Bounding boxes in `[x_min, y_min, x_max, y_max]` pixel coordinates,
201                           ordered by descending confidence.
202    """
203    if not text or not text.strip():
204        raise ValueError("A non-empty text prompt is required for grounding.")
205
206    device = resolve_device(device)
207    model = model.to(device)
208
209    # Grounding DINO is trained on lowercase phrases ending with a period.
210    caption = text.strip().lower()
211    if not caption.endswith("."):
212        caption = caption + "."
213
214    inputs = inputs_to_device(
215        processor(images=image, text=caption, return_tensors="pt"), device
216    )
217
218    with torch.no_grad():
219        outputs = model(**inputs)
220
221    results = processor.post_process_grounded_object_detection(
222        outputs,
223        inputs["input_ids"],
224        threshold=box_threshold,
225        text_threshold=text_threshold,
226        target_sizes=[(image.height, image.width)],
227    )[0]
228
229    boxes = results["boxes"].detach().cpu()
230    scores = results["scores"].detach().cpu()
231
232    if boxes.numel() == 0:
233        raise ValueError(
234            f"Grounding model found no region matching text prompt {text!r} at "
235            f"box_threshold={box_threshold} and text_threshold={text_threshold}. "
236            f"Try lowering the thresholds or rephrasing the prompt."
237        )
238
239    order = torch.argsort(scores, descending=True)
240    boxes = boxes[order]
241    if max_boxes is not None:
242        boxes = boxes[:max_boxes]
243
244    return [[float(v) for v in box] for box in boxes]

Converts a natural-language phrase into bounding boxes for an image.

This is the bridge that gives SAM 1 and SAM 2 text-prompted segmentation. Neither model contains a text encoder, so the phrase is first grounded into boxes by Grounding DINO, and those boxes are then used as ordinary geometric prompts for SAM.

Grounding DINO expects lowercase phrases terminated by a period; the text is normalised to that convention automatically.

Arguments:
  • model (GroundingDinoForObjectDetection): The loaded Grounding DINO model.
  • processor (GroundingDinoProcessor): The matching processor with its tokenizer.
  • image (PIL.Image.Image): The image to ground the phrase against.
  • text (str): The natural-language prompt, e.g. "a shirt" or "trousers".
  • box_threshold (float, optional): Minimum box confidence to keep a detection. Default is 0.3.
  • text_threshold (float, optional): Minimum text-matching score to keep a detection. Default is 0.3.
  • max_boxes (int, optional): If given, keep at most this many highest-scoring boxes. Default is None, meaning keep all boxes above threshold.
  • device (Union[str, torch.device], optional): The device to run grounding on, e.g. "cpu", "cuda", or "mps". Default is "cpu".
Raises:
  • ValueError: If text is empty, if the requested device is unavailable, or if no region of the image matches the phrase at the given thresholds.
Returns:

list[list[float]]: Bounding boxes in [x_min, y_min, x_max, y_max] pixel coordinates, ordered by descending confidence.