From 7ca52da7768b9d8cbb3aa8627bb054be0ee309ae Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Thu, 10 Jul 2025 19:38:35 +0300 Subject: [PATCH 01/29] =?UTF-8?q?feat:=20=F0=9F=9A=80=20add=20support=20fo?= =?UTF-8?q?r=20Google=20Gemini=202.5=20bounding=20box=20and=20mask=20parsi?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 104 ++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 0f254868..6711459b 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -2,7 +2,10 @@ import json import re from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Union - +import base64 +import io +from PIL import Image +from typing import Union, Optional, Tuple import numpy as np from supervision.detection.utils import ( @@ -406,3 +409,102 @@ def from_google_gemini( return np.empty((0, 4)), np.empty((0,), dtype=str) return np.array(xyxy), np.array(labels) + +def from_google_gemini_2_5( + result: str, + resolution_wh: Tuple[int, int], +) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]: + """ + Parse and scale bounding boxes and masks from Google Gemini 2.5 style JSON output. + https://aistudio.google.com/ + https://ai.google.dev/gemini-api/docs/vision?lang=python + + Args: + result: String containing the JSON snippet enclosed by triple backticks. + resolution_wh: (output_width, output_height) to which we rescale the boxes. + + Returns: + xyxy (np.ndarray): An array of shape `(n, 4)` containing + the bounding boxes coordinates in format `[x1, y1, x2, y2]` + class_name: (np.ndarray): An array of shape `(n,)` containing + the class labels for each bounding box + class_id [np.ndarray]: An array of shape `(n,)` containing + the class indices for each bounding box + masks: Optional[np.ndarray]: An array of shape `(n, h, w)` containing + the segmentation masks for each bounding box + """ + w, h = resolution_wh + if w <= 0 or h <= 0: + raise ValueError( + f"Both dimensions in resolution_wh must be positive. Got ({w}, {h})." + ) + + lines = result.splitlines() + for i, line in enumerate(lines): + if line == "```json": + result = "\n".join(lines[i + 1 :]) + result = result.split("```")[0] + break + + try: + data = json.loads(result) + except json.JSONDecodeError: + return np.empty((0, 4)), np.empty((0,), dtype=str), np.empty((0,), dtype=int), None + + class_name: list = [] + class_id: list = [] + xyxy: list = [] + masks: Optional[list] = [] + + for item in data: + if "box_2d" not in item or "label" not in item: + continue + class_name.append(item["label"]) + box = item["box_2d"] + # Gemini bbox order is [y_min, x_min, y_max, x_max] + absolute_bbox = denormalize_boxes( + np.array([box[1], box[0], box[3], box[2]]).astype(np.float64), + resolution_wh=(w, h), + normalization_factor=1000, + ) + xyxy.append(absolute_bbox) + + if "mask" in item: + png_str = item["mask"] + if not png_str.startswith("data:image/png;base64,"): + masks.append(np.zeros((h, w), dtype=bool)) + continue + + png_str = png_str.removeprefix("data:image/png;base64,") + png_str = base64.b64decode(png_str) + mask_img = Image.open(io.BytesIO(png_str)) + + y_min, y_max = int(absolute_bbox[1]), int(absolute_bbox[3]) + x_min, x_max = int(absolute_bbox[0]), int(absolute_bbox[2]) + + bbox_height = y_max - y_min + bbox_width = x_max - x_min + + if bbox_height > 0 and bbox_width > 0: + mask_img = mask_img.resize( + (bbox_width, bbox_height), resample=Image.Resampling.BILINEAR + ) + np_mask = np.zeros((h, w), dtype=bool) + np_mask[y_min:y_max, x_min:x_max] = np.array(mask_img) > 0 + masks.append(np_mask) + else: + masks.append(np.zeros((h, w), dtype=bool)) + else: + masks.append(np.zeros((h, w), dtype=bool)) + + if not xyxy: + return np.empty((0, 4)), np.empty((0,), dtype=str), np.empty((0,), dtype=int), None + + mask = np.array(masks) if masks is not None else None + + unique_labels = list(set(class_name)) + for label in class_name: + class_id.append(unique_labels.index(label)) + + return np.array(xyxy), np.array(class_id), np.array(class_name), mask + From 0e8ec18c20cd90f62f14be6888de5b37763318c2 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Thu, 10 Jul 2025 19:39:00 +0300 Subject: [PATCH 02/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20remove=20not=20exis?= =?UTF-8?q?t=20Google=20Gemini=202.0=20and=202.5=20flash=20preview=20mappi?= =?UTF-8?q?ngs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index af91dc5d..eaba9991 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -899,10 +899,7 @@ class Detections: LMM.FLORENCE_2: VLM.FLORENCE_2, LMM.QWEN_2_5_VL: VLM.QWEN_2_5_VL, LMM.GOOGLE_GEMINI_2_0: VLM.GOOGLE_GEMINI_2_0, - LMM.GOOGLE_GEMINI_2_0_FLASH: VLM.GOOGLE_GEMINI_2_0_FLASH, LMM.GOOGLE_GEMINI_2_5: VLM.GOOGLE_GEMINI_2_5, - LMM.GOOGLE_GEMINI_2_5_FLASH_PREVIEW: VLM.GOOGLE_GEMINI_2_5_FLASH_PREVIEW, - LMM.GOOGLE_GEMINI_2_5_PRO_PREVIEW: VLM.GOOGLE_GEMINI_2_5_PRO_PREVIEW, } # (this works even if the LMM enum is wrapped by @deprecated) From 74ba99479c88b174cacd5010db1e7a7a334aee16 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Thu, 10 Jul 2025 19:44:48 +0300 Subject: [PATCH 03/29] =?UTF-8?q?chore:=20=F0=9F=A7=B9=20clean=20up=20impo?= =?UTF-8?q?rts=20and=20improve=20formatting=20in=20Google=20Gemini=20funct?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 6711459b..6cfdb047 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -1,12 +1,12 @@ +import base64 +import io import json import re from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Union -import base64 -import io -from PIL import Image -from typing import Union, Optional, Tuple + import numpy as np +from PIL import Image from supervision.detection.utils import ( denormalize_boxes, @@ -410,6 +410,7 @@ def from_google_gemini( return np.array(xyxy), np.array(labels) + def from_google_gemini_2_5( result: str, resolution_wh: Tuple[int, int], @@ -449,7 +450,12 @@ def from_google_gemini_2_5( try: data = json.loads(result) except json.JSONDecodeError: - return np.empty((0, 4)), np.empty((0,), dtype=str), np.empty((0,), dtype=int), None + return ( + np.empty((0, 4)), + np.empty((0,), dtype=str), + np.empty((0,), dtype=int), + None, + ) class_name: list = [] class_id: list = [] @@ -463,10 +469,10 @@ def from_google_gemini_2_5( box = item["box_2d"] # Gemini bbox order is [y_min, x_min, y_max, x_max] absolute_bbox = denormalize_boxes( - np.array([box[1], box[0], box[3], box[2]]).astype(np.float64), - resolution_wh=(w, h), - normalization_factor=1000, - ) + np.array([box[1], box[0], box[3], box[2]]).astype(np.float64), + resolution_wh=(w, h), + normalization_factor=1000, + ) xyxy.append(absolute_bbox) if "mask" in item: @@ -498,7 +504,12 @@ def from_google_gemini_2_5( masks.append(np.zeros((h, w), dtype=bool)) if not xyxy: - return np.empty((0, 4)), np.empty((0,), dtype=str), np.empty((0,), dtype=int), None + return ( + np.empty((0, 4)), + np.empty((0,), dtype=str), + np.empty((0,), dtype=int), + None, + ) mask = np.array(masks) if masks is not None else None @@ -507,4 +518,3 @@ def from_google_gemini_2_5( class_id.append(unique_labels.index(label)) return np.array(xyxy), np.array(class_id), np.array(class_name), mask - From 5d2616093d34f2f19c8b95bca5b14adb4a2efdef Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Thu, 10 Jul 2025 19:45:02 +0300 Subject: [PATCH 04/29] =?UTF-8?q?feat:=20=F0=9F=9A=80=20add=20support=20fo?= =?UTF-8?q?r=20Google=20Gemini=202.5=20detection=20processing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index eaba9991..ddcf125e 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -38,6 +38,7 @@ from supervision.detection.vlm import ( VLM, from_florence_2, from_google_gemini, + from_google_gemini_2_5, from_paligemma, from_qwen_2_5_vl, validate_vlm_parameters, @@ -1034,16 +1035,16 @@ class Detections: return cls(xyxy=xyxy, mask=mask, data=data) - if ( - vlm == VLM.GOOGLE_GEMINI_2_0 - or vlm == VLM.GOOGLE_GEMINI_2_5 - or vlm == VLM.GOOGLE_GEMINI_2_5_FLASH_PREVIEW - or vlm == VLM.GOOGLE_GEMINI_2_5_PRO_PREVIEW - ): + if vlm == VLM.GOOGLE_GEMINI_2_0: xyxy, class_name = from_google_gemini(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} return cls(xyxy=xyxy, data=data) + if vlm == VLM.GOOGLE_GEMINI_2_5: + xyxy, class_id, class_name, mask = from_google_gemini_2_5(result, **kwargs) + data = {CLASS_NAME_DATA_FIELD: class_name} + return cls(xyxy=xyxy, class_id=class_id, mask=mask, data=data) + return cls.empty() @classmethod From fd1bb5b4b2c5fe505a32b7ba0612fa76c478e917 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Fri, 11 Jul 2025 16:49:58 +0300 Subject: [PATCH 05/29] Update supervision/detection/vlm.py Co-authored-by: Soumik Rakshit <19soumik.rakshit96@gmail.com> --- supervision/detection/vlm.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 6cfdb047..1267ca74 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -416,9 +416,19 @@ def from_google_gemini_2_5( resolution_wh: Tuple[int, int], ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]: """ - Parse and scale bounding boxes and masks from Google Gemini 2.5 style JSON output. - https://aistudio.google.com/ - https://ai.google.dev/gemini-api/docs/vision?lang=python + Parse and scale bounding boxes and masks from Google Gemini 2.5 style + [JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python). + + The JSON is expected to be enclosed in triple backticks with the format: + ```json + [ + { + "box_2d": [x1, y1, x2, y2], + "mask": "data:image/png;base64,...", + "label": "some class name"}, + ... + ] + ``` Args: result: String containing the JSON snippet enclosed by triple backticks. From a6d9550a9a5bed4e0020efde64b6054407af7ddf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:51:19 +0000 Subject: [PATCH 06/29] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 1267ca74..193c52ef 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -418,7 +418,7 @@ def from_google_gemini_2_5( """ Parse and scale bounding boxes and masks from Google Gemini 2.5 style [JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python). - + The JSON is expected to be enclosed in triple backticks with the format: ```json [ From d34f0506425e7dd3caeb0384d12b98a4e6165835 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Fri, 11 Jul 2025 17:40:25 +0300 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20=F0=9F=9A=80=20implement=20resolu?= =?UTF-8?q?tion=20validation=20function=20and=20refactor=20related=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 30 +++++++----------------------- supervision/validators/__init__.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 193c52ef..ed62a449 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -14,6 +14,7 @@ from supervision.detection.utils import ( polygon_to_xyxy, ) from supervision.utils.internal import deprecated +from supervision.validators import validate_resolution @deprecated( @@ -126,11 +127,7 @@ def from_paligemma( the class labels for each bounding box. """ - w, h = resolution_wh - if w <= 0 or h <= 0: - raise ValueError( - f"Both dimensions in resolution_wh must be positive. Got ({w}, {h})." - ) + w, h = validate_resolution(resolution_wh) pattern = re.compile( r"(?) ([\w\s\-]+)" @@ -189,14 +186,9 @@ def from_qwen_2_5_vl( class_name (np.ndarray): An array of shape `(n,)` containing the class labels for each bounding box """ - in_w, in_h = input_wh - out_w, out_h = resolution_wh - if in_w <= 0 or in_h <= 0 or out_w <= 0 or out_h <= 0: - raise ValueError( - f"Both input and resolution dimensions must be positive. " - f"Got input_wh=({in_w}, {in_h}), resolution_wh=({out_w}, {out_h})." - ) + in_w, in_h = validate_resolution(input_wh) + out_w, out_h = validate_resolution(resolution_wh) pattern = re.compile(r"```json\s*(.*?)\s*```", re.DOTALL) @@ -325,7 +317,7 @@ def from_florence_2( f"Expected string to end in location tags, but got {result}" ) - w, h = resolution_wh + w, h = validate_resolution(resolution_wh) xyxy = np.array([match.groups()], dtype=np.float32) xyxy *= np.array([w, h, w, h]) / 1000 result_string = result[: match.start()] @@ -371,11 +363,7 @@ def from_google_gemini( """ - w, h = resolution_wh - if w <= 0 or h <= 0: - raise ValueError( - f"Both dimensions in resolution_wh must be positive. Got ({w}, {h})." - ) + w, h = validate_resolution(resolution_wh) lines = result.splitlines() for i, line in enumerate(lines): @@ -444,11 +432,7 @@ def from_google_gemini_2_5( masks: Optional[np.ndarray]: An array of shape `(n, h, w)` containing the segmentation masks for each bounding box """ - w, h = resolution_wh - if w <= 0 or h <= 0: - raise ValueError( - f"Both dimensions in resolution_wh must be positive. Got ({w}, {h})." - ) + w, h = validate_resolution(resolution_wh) lines = result.splitlines() for i, line in enumerate(lines): diff --git a/supervision/validators/__init__.py b/supervision/validators/__init__.py index 29ac0da9..7b1f00eb 100644 --- a/supervision/validators/__init__.py +++ b/supervision/validators/__init__.py @@ -138,3 +138,13 @@ def validate_keypoints_fields( validate_class_id(class_id, n) validate_keypoint_confidence(confidence, n, m) validate_data(data, n) + + +def validate_resolution(resolution): + w, h = resolution + if w <= 0 or h <= 0: + raise ValueError( + f"Both dimensions in resolution must be positive. Got ({w}, {h})." + ) + + return w, h From c7ebfeb4bc72f1e601525526e2c2793d69cb34b2 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 10:58:59 +0300 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=F0=9F=9A=80=20enhance=20Google?= =?UTF-8?q?=20Gemini=202.5=20integration=20to=20include=20confidence=20sco?= =?UTF-8?q?res=20in=20detection=20results?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 4 ++-- supervision/detection/vlm.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index ddcf125e..d3d593a6 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1041,9 +1041,9 @@ class Detections: return cls(xyxy=xyxy, data=data) if vlm == VLM.GOOGLE_GEMINI_2_5: - xyxy, class_id, class_name, mask = from_google_gemini_2_5(result, **kwargs) + xyxy, class_id, class_name, mask, confidence = from_google_gemini_2_5(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} - return cls(xyxy=xyxy, class_id=class_id, mask=mask, data=data) + return cls(xyxy=xyxy, class_id=class_id, mask=mask, confidence=confidence, data=data) return cls.empty() diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index ed62a449..3d8a3238 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -402,7 +402,7 @@ def from_google_gemini( def from_google_gemini_2_5( result: str, resolution_wh: Tuple[int, int], -) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray]]: +) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]: """ Parse and scale bounding boxes and masks from Google Gemini 2.5 style [JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python). @@ -454,7 +454,8 @@ def from_google_gemini_2_5( class_name: list = [] class_id: list = [] xyxy: list = [] - masks: Optional[list] = [] + masks: list = [] + confidence: list = [] for item in data: if "box_2d" not in item or "label" not in item: @@ -497,6 +498,14 @@ def from_google_gemini_2_5( else: masks.append(np.zeros((h, w), dtype=bool)) + + if "confidence" in item: + # if confidence is provided + confidence.append(item["confidence"]) + else: + # if confidence is not provided, we assume 0 + confidence.append(0.0) + if not xyxy: return ( np.empty((0, 4)), @@ -511,4 +520,4 @@ def from_google_gemini_2_5( for label in class_name: class_id.append(unique_labels.index(label)) - return np.array(xyxy), np.array(class_id), np.array(class_name), mask + return np.array(xyxy), np.array(class_id), np.array(class_name), mask, np.array(confidence) From 3152f5f0ea6ff399f3c2f4a361fc0fc82c65cfed Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 10:59:49 +0300 Subject: [PATCH 09/29] =?UTF-8?q?docs:=20=F0=9F=93=9D=20add=20docs=20to=20?= =?UTF-8?q?confidence=20scores=20parameter=20to=20from=5Fgoogle=5Fgemini?= =?UTF-8?q?=5F2=5F5=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 3d8a3238..80335cec 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -431,6 +431,9 @@ def from_google_gemini_2_5( the class indices for each bounding box masks: Optional[np.ndarray]: An array of shape `(n, h, w)` containing the segmentation masks for each bounding box + confidence: Optional[np.ndarray]: An array of shape `(n,)` containing + the confidence scores for each bounding box. If not provided, + it defaults to 0.0 for each box. """ w, h = validate_resolution(resolution_wh) From 6bae8bc6e60d540a520635e9413090314496b4c2 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 11:02:19 +0300 Subject: [PATCH 10/29] =?UTF-8?q?formatting:=20=20=F0=9F=A7=B9=20code=20re?= =?UTF-8?q?adability=20by=20formatting=20return=20statements=20and=20funct?= =?UTF-8?q?ion=20signatures=20in=20Google=20Gemini=202.5=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 12 ++++++++++-- supervision/detection/vlm.py | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index d3d593a6..d030d853 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1041,9 +1041,17 @@ class Detections: return cls(xyxy=xyxy, data=data) if vlm == VLM.GOOGLE_GEMINI_2_5: - xyxy, class_id, class_name, mask, confidence = from_google_gemini_2_5(result, **kwargs) + xyxy, class_id, class_name, mask, confidence = from_google_gemini_2_5( + result, **kwargs + ) data = {CLASS_NAME_DATA_FIELD: class_name} - return cls(xyxy=xyxy, class_id=class_id, mask=mask, confidence=confidence, data=data) + return cls( + xyxy=xyxy, + class_id=class_id, + mask=mask, + confidence=confidence, + data=data, + ) return cls.empty() diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 80335cec..bd9c5399 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -402,7 +402,9 @@ def from_google_gemini( def from_google_gemini_2_5( result: str, resolution_wh: Tuple[int, int], -) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]: +) -> Tuple[ + np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], Optional[np.ndarray] +]: """ Parse and scale bounding boxes and masks from Google Gemini 2.5 style [JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python). @@ -501,7 +503,6 @@ def from_google_gemini_2_5( else: masks.append(np.zeros((h, w), dtype=bool)) - if "confidence" in item: # if confidence is provided confidence.append(item["confidence"]) @@ -523,4 +524,10 @@ def from_google_gemini_2_5( for label in class_name: class_id.append(unique_labels.index(label)) - return np.array(xyxy), np.array(class_id), np.array(class_name), mask, np.array(confidence) + return ( + np.array(xyxy), + np.array(class_id), + np.array(class_name), + mask, + np.array(confidence), + ) From 5d866fd8a190368a765783581b344df536ec49aa Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 14:14:03 +0300 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20=20=E2=9C=A8=20=20improve=20v?= =?UTF-8?q?alidate=5Fresolution=20function=20to=20have=20type,value=20chec?= =?UTF-8?q?k=20similar=20to=20other=20validate=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/validators/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/supervision/validators/__init__.py b/supervision/validators/__init__.py index 7b1f00eb..4d21493b 100644 --- a/supervision/validators/__init__.py +++ b/supervision/validators/__init__.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, Tuple import numpy as np @@ -140,11 +140,18 @@ def validate_keypoints_fields( validate_data(data, n) -def validate_resolution(resolution): +def validate_resolution(resolution: Any) -> Tuple[int, int]: + if not (isinstance(resolution, tuple) and len(resolution) == 2): + raise ValueError( + f"resolution must be a tuple of two integers, got {type(resolution)} with value {resolution}" + ) w, h = resolution + if not (isinstance(w, int) and isinstance(h, int)): + raise ValueError( + f"Both elements in resolution must be integers. Got types ({type(w)}, {type(h)})" + ) if w <= 0 or h <= 0: raise ValueError( f"Both dimensions in resolution must be positive. Got ({w}, {h})." ) - return w, h From 4ed667da88fed7dfaef43ae3239f43af0985e6ba Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 14:24:37 +0300 Subject: [PATCH 12/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20rename=20from=5Fgoo?= =?UTF-8?q?gle=5Fgemini=20to=20from=5Fgoogle=5Fgemini=5F2=5F0=20keep=20nam?= =?UTF-8?q?es=20consistent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 4 ++-- supervision/detection/vlm.py | 2 +- supervision/validators/__init__.py | 10 ++++++++-- test/detection/test_vlm.py | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 07cb2618..b0a4fe63 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -37,7 +37,7 @@ from supervision.detection.vlm import ( LMM, VLM, from_florence_2, - from_google_gemini, + from_google_gemini_2_0, from_google_gemini_2_5, from_moondream, from_paligemma, @@ -1137,7 +1137,7 @@ class Detections: return cls(xyxy=xyxy, mask=mask, data=data) if vlm == VLM.GOOGLE_GEMINI_2_0 or vlm == VLM.GOOGLE_GEMINI_2_5: - xyxy, class_id, class_name = from_google_gemini(result, **kwargs) + xyxy, class_id, class_name = from_google_gemini_2_0(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} return cls(xyxy=xyxy, class_id=class_id, data=data) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 4be54be5..a9f6e781 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -330,7 +330,7 @@ def from_florence_2( assert False, f"Unimplemented task: {task}" -def from_google_gemini( +def from_google_gemini_2_0( result: str, resolution_wh: Tuple[int, int], classes: Optional[List[str]] = None, diff --git a/supervision/validators/__init__.py b/supervision/validators/__init__.py index 4d21493b..f40b6f83 100644 --- a/supervision/validators/__init__.py +++ b/supervision/validators/__init__.py @@ -143,12 +143,18 @@ def validate_keypoints_fields( def validate_resolution(resolution: Any) -> Tuple[int, int]: if not (isinstance(resolution, tuple) and len(resolution) == 2): raise ValueError( - f"resolution must be a tuple of two integers, got {type(resolution)} with value {resolution}" + f""" + resolution must be a tuple of two integers, got + {type(resolution)} with value {resolution} + """ ) w, h = resolution if not (isinstance(w, int) and isinstance(h, int)): raise ValueError( - f"Both elements in resolution must be integers. Got types ({type(w)}, {type(h)})" + f""" + Both elements in resolution must be integers. + Got types ({type(w)}, {type(h)}) + """ ) if w <= 0 or h <= 0: raise ValueError( diff --git a/test/detection/test_vlm.py b/test/detection/test_vlm.py index 7b9acb0c..c2762f5d 100644 --- a/test/detection/test_vlm.py +++ b/test/detection/test_vlm.py @@ -7,7 +7,7 @@ import pytest from supervision.detection.vlm import ( from_florence_2, - from_google_gemini, + from_google_gemini_2_0, from_moondream, from_paligemma, from_qwen_2_5_vl, @@ -492,7 +492,7 @@ def test_from_google_gemini( expected_results: Tuple[np.ndarray, Optional[np.ndarray], np.ndarray], ) -> None: with exception: - xyxy, class_id, class_name = from_google_gemini( + xyxy, class_id, class_name = from_google_gemini_2_0( result=result, resolution_wh=resolution_wh, classes=classes ) if expected_results is not None: From 3b112ffb46b82b7750f999d74333fd69ba98ec58 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 15:11:12 +0300 Subject: [PATCH 13/29] =?UTF-8?q?refactor:=20=E2=9C=A8=20update=20from=5Fg?= =?UTF-8?q?oogle=5Fgemini=5F2=5F5=20function=20to=20include=20optional=20c?= =?UTF-8?q?lasses=20parameter=20and=20adjust=20return=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 2 +- supervision/detection/vlm.py | 51 ++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index b0a4fe63..2478721b 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1146,7 +1146,7 @@ class Detections: return cls(xyxy=xyxy) if vlm == VLM.GOOGLE_GEMINI_2_5: - xyxy, class_id, class_name, mask, confidence = from_google_gemini_2_5( + xyxy, class_id, class_name, confidence, mask = from_google_gemini_2_5( result, **kwargs ) data = {CLASS_NAME_DATA_FIELD: class_name} diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index a9f6e781..bc9e6f6b 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -421,6 +421,7 @@ def from_google_gemini_2_0( def from_google_gemini_2_5( result: str, resolution_wh: Tuple[int, int], + classes: Optional[List[str]] = None ) -> Tuple[ np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], Optional[np.ndarray] ]: @@ -445,15 +446,15 @@ def from_google_gemini_2_5( Returns: xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes coordinates in format `[x1, y1, x2, y2]` - class_name: (np.ndarray): An array of shape `(n,)` containing - the class labels for each bounding box - class_id [np.ndarray]: An array of shape `(n,)` containing + class_id (np.ndarray): An array of shape `(n,)` containing the class indices for each bounding box - masks: Optional[np.ndarray]: An array of shape `(n, h, w)` containing - the segmentation masks for each bounding box + class_name (np.ndarray): An array of shape `(n,)` containing + the class labels for each bounding box confidence: Optional[np.ndarray]: An array of shape `(n,)` containing the confidence scores for each bounding box. If not provided, it defaults to 0.0 for each box. + masks (Optional[np.ndarray]): An array of shape `(n, h, w)` containing + the segmentation masks for each bounding box """ w, h = validate_resolution(resolution_wh) @@ -472,13 +473,14 @@ def from_google_gemini_2_5( np.empty((0,), dtype=str), np.empty((0,), dtype=int), None, + ) - class_name: list = [] - class_id: list = [] xyxy: list = [] - masks: list = [] + class_id: list = [] + class_name: list = [] confidence: list = [] + masks: list = [] for item in data: if "box_2d" not in item or "label" not in item: @@ -522,32 +524,34 @@ def from_google_gemini_2_5( masks.append(np.zeros((h, w), dtype=bool)) if "confidence" in item: - # if confidence is provided confidence.append(item["confidence"]) else: - # if confidence is not provided, we assume 0 confidence.append(0.0) if not xyxy: return ( np.empty((0, 4)), - np.empty((0,), dtype=str), - np.empty((0,), dtype=int), + np.array([], dtype=int), + np.array([], dtype=str), + np.array([], dtype=np.float32), None, ) - mask = np.array(masks) if masks is not None else None - unique_labels = list(set(class_name)) - for label in class_name: - class_id.append(unique_labels.index(label)) + if classes is not None: + mask = np.array([name in classes for name in class_name], dtype=bool) + xyxy = xyxy[mask] + class_name = class_name[mask] + class_id = np.array([classes.index(name) for name in class_name], dtype=int) + masks = [masks[i] for i in range(len(masks)) if mask[i]] + return ( - np.array(xyxy), - np.array(class_id), - np.array(class_name), - mask, - np.array(confidence), + np.array(xyxy, dtype=float), + np.array(class_id, dtype=int), + np.array(class_name, dtype=str), + np.array(confidence, dtype=float), + np.array(masks) if masks is not None else None, ) @@ -574,10 +578,13 @@ def from_moondream( ] } - Args: result: Dictionary containing the JSON output from the model. resolution_wh: (output_width, output_height) to which we rescale the boxes. + + Returns: + xyxy (np.ndarray): An array of shape `(n, 4)` containing + the bounding boxes coordinates in format `[x1, y1, x2, y2]` """ w, h = resolution_wh From 377f7db0ad3b20eb04e625e6868216ef5fbf5137 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 15:22:10 +0300 Subject: [PATCH 14/29] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20confidence=20fi?= =?UTF-8?q?eld=20to=20the=20result=20of=20from=5Fgoogle=5Fgemini=5F2=5F5?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index bc9e6f6b..acee6826 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -435,7 +435,9 @@ def from_google_gemini_2_5( { "box_2d": [x1, y1, x2, y2], "mask": "data:image/png;base64,...", - "label": "some class name"}, + "label": "some class name", + "confidence": 0.95, + }, ... ] ``` From 6c4a2a8d44efb624d5e2467ac729b9d9c8e70109 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 16:50:41 +0300 Subject: [PATCH 15/29] =?UTF-8?q?test:=20=F0=9F=A7=AA=20add=20gemini=5F2?= =?UTF-8?q?=5F5=20test=20and=20fix=20failed=20cases=20in=20gemini=5F2=5F5?= =?UTF-8?q?=20function=20for=20better=20to=20handle=20different=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- supervision/detection/vlm.py | 118 ++++++++++++-------- test/detection/test_vlm.py | 202 +++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 47 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index acee6826..1294b1d4 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -421,9 +421,13 @@ def from_google_gemini_2_0( def from_google_gemini_2_5( result: str, resolution_wh: Tuple[int, int], - classes: Optional[List[str]] = None + classes: Optional[List[str]] = None, ) -> Tuple[ - np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], Optional[np.ndarray] + np.ndarray, + Optional[np.ndarray], + np.ndarray, + Optional[np.ndarray], + Optional[np.ndarray], ]: """ Parse and scale bounding boxes and masks from Google Gemini 2.5 style @@ -444,6 +448,9 @@ def from_google_gemini_2_5( Args: result: String containing the JSON snippet enclosed by triple backticks. + resolution_wh: (output_width, output_height) to which we rescale the boxes. + classes: Optional list of valid class names. If provided, returned boxes/labels + are filtered to only those classes found here. Returns: xyxy (np.ndarray): An array of shape `(n, 4)` containing @@ -472,22 +479,21 @@ def from_google_gemini_2_5( except json.JSONDecodeError: return ( np.empty((0, 4)), - np.empty((0,), dtype=str), - np.empty((0,), dtype=int), + np.array([], dtype=int), + np.array([], dtype=str), + np.array([], dtype=float), None, - ) - xyxy: list = [] - class_id: list = [] - class_name: list = [] - confidence: list = [] - masks: list = [] + xyxy_list: list = [] + labels_list: list = [] + confidence_list: Optional[list] = [] + masks_list: Optional[list] = [] for item in data: if "box_2d" not in item or "label" not in item: continue - class_name.append(item["label"]) + labels_list.append(item["label"]) box = item["box_2d"] # Gemini bbox order is [y_min, x_min, y_max, x_max] absolute_bbox = denormalize_boxes( @@ -495,65 +501,83 @@ def from_google_gemini_2_5( resolution_wh=(w, h), normalization_factor=1000, ) - xyxy.append(absolute_bbox) + xyxy_list.append(absolute_bbox) if "mask" in item: - png_str = item["mask"] - if not png_str.startswith("data:image/png;base64,"): - masks.append(np.zeros((h, w), dtype=bool)) - continue + if masks_list is not None: + png_str = item["mask"] + if not png_str.startswith("data:image/png;base64,"): + masks_list.append(np.zeros((h, w), dtype=bool)) + continue - png_str = png_str.removeprefix("data:image/png;base64,") - png_str = base64.b64decode(png_str) - mask_img = Image.open(io.BytesIO(png_str)) + png_str = png_str.removeprefix("data:image/png;base64,") + png_str = base64.b64decode(png_str) + mask_img = Image.open(io.BytesIO(png_str)) - y_min, y_max = int(absolute_bbox[1]), int(absolute_bbox[3]) - x_min, x_max = int(absolute_bbox[0]), int(absolute_bbox[2]) + y_min, y_max = int(absolute_bbox[1]), int(absolute_bbox[3]) + x_min, x_max = int(absolute_bbox[0]), int(absolute_bbox[2]) - bbox_height = y_max - y_min - bbox_width = x_max - x_min + bbox_height = y_max - y_min + bbox_width = x_max - x_min - if bbox_height > 0 and bbox_width > 0: - mask_img = mask_img.resize( - (bbox_width, bbox_height), resample=Image.Resampling.BILINEAR - ) - np_mask = np.zeros((h, w), dtype=bool) - np_mask[y_min:y_max, x_min:x_max] = np.array(mask_img) > 0 - masks.append(np_mask) - else: - masks.append(np.zeros((h, w), dtype=bool)) + if bbox_height > 0 and bbox_width > 0: + mask_img = mask_img.resize( + (bbox_width, bbox_height), resample=Image.Resampling.BILINEAR + ) + np_mask = np.zeros((h, w), dtype=bool) + np_mask[y_min:y_max, x_min:x_max] = np.array(mask_img) > 0 + masks_list.append(np_mask) + else: + masks_list.append(np.zeros((h, w), dtype=bool)) else: - masks.append(np.zeros((h, w), dtype=bool)) + masks_list = None if "confidence" in item: - confidence.append(item["confidence"]) + if confidence_list is not None: + confidence_list.append(item["confidence"]) else: - confidence.append(0.0) + confidence_list = None - if not xyxy: + if not xyxy_list: return ( np.empty((0, 4)), - np.array([], dtype=int), + np.array([], dtype=int), np.array([], dtype=str), - np.array([], dtype=np.float32), + np.array([], dtype=float), None, ) + xyxy = np.array(xyxy_list, dtype=float) + class_name = np.array(labels_list) + class_id: np.ndarray if classes is not None: mask = np.array([name in classes for name in class_name], dtype=bool) xyxy = xyxy[mask] class_name = class_name[mask] - class_id = np.array([classes.index(name) for name in class_name], dtype=int) - masks = [masks[i] for i in range(len(masks)) if mask[i]] + class_id = np.array([classes.index(name) for name in class_name]) + if masks_list is not None: + masks_list = [masks_list[i] for i, m in enumerate(mask) if m] + if confidence_list is not None: + confidence_list = [c for c, m in zip(confidence_list, mask) if m] + else: + # When classes is None, generate class_id based on unique labels + unique_labels = sorted(list(set(class_name))) + label_to_id = {label: i for i, label in enumerate(unique_labels)} + class_id = np.array([label_to_id[name] for name in class_name]) + + confidence = ( + np.array(confidence_list, dtype=float) if confidence_list is not None else None + ) + masks = np.array(masks_list) if masks_list is not None else None return ( - np.array(xyxy, dtype=float), - np.array(class_id, dtype=int), - np.array(class_name, dtype=str), - np.array(confidence, dtype=float), - np.array(masks) if masks is not None else None, + xyxy, + class_id, + class_name, + confidence, + masks, ) @@ -583,7 +607,7 @@ def from_moondream( Args: result: Dictionary containing the JSON output from the model. resolution_wh: (output_width, output_height) to which we rescale the boxes. - + Returns: xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes coordinates in format `[x1, y1, x2, y2]` @@ -596,7 +620,7 @@ def from_moondream( ) if "objects" not in result or not isinstance(result["objects"], list): - return np.empty((0, 4)) + return np.empty((0, 4), dtype=float) denormalize_xyxy = [] diff --git a/test/detection/test_vlm.py b/test/detection/test_vlm.py index c2762f5d..2135600b 100644 --- a/test/detection/test_vlm.py +++ b/test/detection/test_vlm.py @@ -8,6 +8,7 @@ import pytest from supervision.detection.vlm import ( from_florence_2, from_google_gemini_2_0, + from_google_gemini_2_5, from_moondream, from_paligemma, from_qwen_2_5_vl, @@ -883,3 +884,204 @@ def test_florence_2( assert result[3] is None else: np.testing.assert_array_equal(result[3], expected_results[3]) + + +@pytest.mark.parametrize( + "exception, result, resolution_wh, classes, expected_results", + [ + ( + does_not_raise(), + "random text", + (1000, 1000), + None, + ( + np.empty((0, 4)), + np.empty(0, dtype=int), + np.empty(0, dtype=str), + np.empty(0, dtype=float), + None, + ), + ), + ( + does_not_raise(), + "```json\ninvalid json\n```", + (1000, 1000), + None, + ( + np.empty((0, 4)), + np.empty(0, dtype=int), + np.empty(0, dtype=str), + np.empty(0, dtype=float), + None, + ), + ), + ( + does_not_raise(), + "```json\n[]\n```", + (1000, 1000), + None, + ( + np.empty((0, 4)), + np.empty(0, dtype=int), + np.empty(0, dtype=str), + np.empty(0, dtype=float), + None, + ), + ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [100, 200, 300, 400], "label": "cat", "confidence": 0.8} + ] + ```""", + (1000, 500), + None, + ( + np.array([[200.0, 50.0, 400.0, 150.0]]), + np.array([0]), + np.array(["cat"], dtype=str), + np.array([0.8]), + None, + ), + ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat", "confidence": 0.8}, + {"box_2d": [50, 100, 150, 200], "label": "dog", "confidence": 0.9} + ] + ```""", + (640, 480), + None, + ( + np.array([[12.8, 4.8, 76.8, 52.8], [64.0, 24.0, 128.0, 72.0]]), + np.array([0, 1]), + np.array(["cat", "dog"], dtype=str), + np.array([0.8, 0.9]), + None, + ), + ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat", "confidence": 0.8} + ] + ```""", + (640, 480), + ["dog", "person"], + ( + np.empty((0, 4)), + np.empty(0, dtype=int), + np.empty(0, dtype=str), + np.empty(0, dtype=float), + None, + ), + ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat", "confidence": 0.8}, + {"box_2d": [50, 100, 150, 200], "label": "dog", "confidence": 0.9} + ] + ```""", + (640, 480), + ["person", "dog"], + ( + np.array([[64.0, 24.0, 128.0, 72.0]]), + np.array([1]), + np.array(["dog"], dtype=str), + np.array([0.9]), + None, + ), + ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat", "confidence": 0.8}, + {"box_2d": [50, 100, 150, 200], "label": "dog", "confidence": 0.9} + ] + ```""", + (640, 480), + ["cat", "dog"], + ( + np.array([[12.8, 4.8, 76.8, 52.8], [64.0, 24.0, 128.0, 72.0]]), + np.array([0, 1]), + np.array(["cat", "dog"]), + np.array([0.8, 0.9]), + None, + ), + ), + ( + pytest.raises(ValueError), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat"} + ] + ```""", + (0, 480), + None, + None, + ), + ( + pytest.raises(ValueError), + """```json + [ + {"box_2d": [10, 20, 110, 120], "label": "cat"} + ] + ```""", + (640, -100), + None, + None, + ), + ], +) +def test_from_google_gemini_2_5( + exception, + result: str, + resolution_wh: Tuple[int, int], + classes: Optional[List[str]], + expected_results: Optional[ + Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray] + ], +): + with exception: + ( + xyxy, + class_id, + class_name, + confidence, + masks, + ) = from_google_gemini_2_5( + result=result, resolution_wh=resolution_wh, classes=classes + ) + + if expected_results is None: + return + + assert xyxy.shape == expected_results[0].shape + assert np.allclose(xyxy, expected_results[0]) + + assert class_id.shape == expected_results[1].shape + assert np.array_equal(class_id, expected_results[1]) + + assert class_name.shape == expected_results[2].shape + assert np.array_equal(class_name, expected_results[2]) + + if confidence is None: + assert expected_results[3] is None + else: + assert expected_results[3] is not None + assert confidence.shape == expected_results[3].shape + assert np.allclose(confidence, expected_results[3]) + + if masks is None: + assert expected_results[4] is None + else: + assert masks is not None + assert masks.shape == expected_results[4].shape + assert np.array_equal(masks, expected_results[4]) From 076f9e293f0b337f9c029d80dcc9589e9594f1f5 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 17:18:03 +0300 Subject: [PATCH 16/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20mask=20filter?= =?UTF-8?q?ing=20in=20from=5Fgoogle=5Fgemini=5F2=5F5=20to=20detect=20prope?= =?UTF-8?q?rly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 1294b1d4..2d3806ec 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -557,10 +557,10 @@ def from_google_gemini_2_5( class_name = class_name[mask] class_id = np.array([classes.index(name) for name in class_name]) if masks_list is not None: - masks_list = [masks_list[i] for i, m in enumerate(mask) if m] + masks_list = [m for m, keep in zip(masks_list, mask) if keep] if confidence_list is not None: - confidence_list = [c for c, m in zip(confidence_list, mask) if m] + confidence_list = [c for c, keep in zip(confidence_list, mask) if keep] else: # When classes is None, generate class_id based on unique labels unique_labels = sorted(list(set(class_name))) From b87a504d22ab4caed31007b03dca2eae762dfa6e Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 17:26:59 +0300 Subject: [PATCH 17/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20remove=20GOOGLE=5FG?= =?UTF-8?q?EMINI=5F2=5F5=20from=20gemini=5F2=5F0=20check=20and=20only=20us?= =?UTF-8?q?e=20new=20gemini=5F2=5F5=20func?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 2478721b..246ff288 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1136,7 +1136,7 @@ class Detections: return cls(xyxy=xyxy, mask=mask, data=data) - if vlm == VLM.GOOGLE_GEMINI_2_0 or vlm == VLM.GOOGLE_GEMINI_2_5: + if vlm == VLM.GOOGLE_GEMINI_2_0: xyxy, class_id, class_name = from_google_gemini_2_0(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} return cls(xyxy=xyxy, class_id=class_id, data=data) From 129eb89c20fe19c58ab5ee5fc306950ce1c893d2 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 17:29:25 +0300 Subject: [PATCH 18/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20add=20missing=20new?= =?UTF-8?q?line=20at=20end=20of=20extra.css=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/stylesheets/extra.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index a341cda6..2910d5ff 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -267,4 +267,4 @@ th, td { .md-typeset__table table:not([class]) td, .md-typeset__table table:not([class]) th { padding: 10px; -} \ No newline at end of file +} From 2c63511af6243c5c7c77cd26eaf6cd4136bc7854 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 17:53:39 +0300 Subject: [PATCH 19/29] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20usage?= =?UTF-8?q?=20tips=20and=20examples=20for=20Google=20Gemini=202.5=20in=20c?= =?UTF-8?q?ore.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 176 ++++++++++++++++++++++++++++++++++ supervision/detection/vlm.py | 1 - 2 files changed, 176 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 246ff288..677a0a12 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -901,8 +901,94 @@ class Detections: detections.xyxy # array([[543., 40., 728., 200.], [653., 352., 820., 522.]]) + detections.data + # {'class_name': array(['cat', 'dog'], dtype=' Date: Mon, 14 Jul 2025 14:57:21 +0000 Subject: [PATCH 20/29] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 677a0a12..4ecb98ba 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -923,7 +923,7 @@ class Detections: For each detected object, provide: - "label": exact class name from the list above - - "confidence": how certain you are (0.0 to 1.0) + - "confidence": how certain you are (0.0 to 1.0) - "box_2d": bounding box [ymin, xmin, ymax, xmax] normalized 0-1000 - "mask": binary mask of the object in the image, as a base64 encoded string @@ -938,7 +938,7 @@ class Detections: "mask": "..." }, { - "label": "kite", + "label": "kite", "confidence": 0.80, "box_2d": [50, 150, 250, 350], "mask": "..." @@ -996,7 +996,7 @@ class Detections: detections.class_id # array([0, 1]) ``` - + Examples: ```python @@ -1179,7 +1179,7 @@ class Detections: For each detected object, provide: - "label": exact class name from the list above - - "confidence": how certain you are (0.0 to 1.0) + - "confidence": how certain you are (0.0 to 1.0) - "box_2d": bounding box [ymin, xmin, ymax, xmax] normalized 0-1000 - "mask": binary mask of the object in the image, as a base64 encoded string @@ -1194,7 +1194,7 @@ class Detections: "mask": "..." }, { - "label": "kite", + "label": "kite", "confidence": 0.80, "box_2d": [50, 150, 250, 350], "mask": "..." From 8c4a1e5a5e7f585148f8c5b76334daf2ae954362 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 17:58:40 +0300 Subject: [PATCH 21/29] =?UTF-8?q?fix:=20=F0=9F=90=9E=20correct=20formattin?= =?UTF-8?q?g=20and=20remove=20trailing=20whitespace=20in=20core.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 4ecb98ba..0ab7f03b 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1029,7 +1029,7 @@ class Detections: # array([[1752.28, 818.82, 2165.72, 1229.14], # [1908.01, 1346.67, 2585.99, 2024.11]]) ``` - """ + """ # noqa: E501 # filler logic mapping old from_lmm to new from_vlm lmm_to_vlm = { @@ -1286,7 +1286,7 @@ class Detections: ``` - """ + """ # noqa: E501 vlm = validate_vlm_parameters(vlm, result, kwargs) if vlm == VLM.PALIGEMMA: From c2abb24c2641b0c4a2a7afe0ac2b83c0df9d806d Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 18:36:07 +0300 Subject: [PATCH 22/29] =?UTF-8?q?test:=20=F0=9F=A7=AA=20add=20test=20case?= =?UTF-8?q?=20for=20gemini=202.5=20segmentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/detection/test_vlm.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/detection/test_vlm.py b/test/detection/test_vlm.py index 2135600b..d18e03b4 100644 --- a/test/detection/test_vlm.py +++ b/test/detection/test_vlm.py @@ -1038,6 +1038,23 @@ def test_florence_2( None, None, ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [10, 20, 110, 120], "mask": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAADElEQVR4nGNgoCcAAABuAAFIXXpjAAAAAElFTkSuQmCC", "label": "cat"} + ] + ```""", # noqa E501 // docs + (10, 10), + ["cat"], + ( + np.array([[0.2, 0.1, 1.2, 1.1]]), + np.array([0]), + np.array(["cat"]), + None, + np.array([np.zeros((10, 10), dtype=bool)]), + ), + ), ], ) def test_from_google_gemini_2_5( From d29fb8ad056cda3773a476fbf851522b9cff00c2 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 18:48:17 +0300 Subject: [PATCH 23/29] =?UTF-8?q?test:=20=F0=9F=A7=AA=20add=20test=20case?= =?UTF-8?q?=20for=20Google=20Gemini=202.5=20with=20sample=20JSON=20output?= =?UTF-8?q?=20for=202=20masks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/detection/test_vlm.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/detection/test_vlm.py b/test/detection/test_vlm.py index d18e03b4..6464c234 100644 --- a/test/detection/test_vlm.py +++ b/test/detection/test_vlm.py @@ -1055,6 +1055,26 @@ def test_florence_2( np.array([np.zeros((10, 10), dtype=bool)]), ), ), + ( + does_not_raise(), + """```json + [ + {"box_2d": [100, 100, 200, 200], "mask": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAADElEQVR4nGNgoCcAAABuAAFIXXpjAAAAAElFTkSuQmCC", "label": "cat", "confidence": 0.8}, + {"box_2d": [300, 300, 400, 400], "mask": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAAAAACoWZBhAAAADElEQVR4nGNgoCcAAABuAAFIXXpjAAAAAElFTkSuQmCC", "label": "dog", "confidence": 0.9} + ] + ```""", # noqa E501 // docs + (10, 10), + ["cat", "dog"], + ( + np.array([[1.0, 1.0, 2.0, 2.0], [3.0, 3.0, 4.0, 4.0]]), + np.array([0, 1]), + np.array(["cat", "dog"]), + np.array([0.8, 0.9]), + np.array( + [np.zeros((10, 10), dtype=bool), np.zeros((10, 10), dtype=bool)], + ), + ), + ), ], ) def test_from_google_gemini_2_5( From 328c34ab8bc0a10b6cd07ae7540e7d534aee47f9 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 19:59:44 +0300 Subject: [PATCH 24/29] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20MOONDREAM=20enu?= =?UTF-8?q?m=20and=20allowed=20arguments=20in=20VLM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 09fa580d..664bb6f7 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -27,6 +27,7 @@ class LMM(Enum): QWEN_2_5_VL = "qwen_2_5_vl" GOOGLE_GEMINI_2_0 = "gemini_2_0" GOOGLE_GEMINI_2_5 = "gemini_2_5" + MOONDREAM = "moondream" class VLM(Enum): @@ -62,6 +63,7 @@ ALLOWED_ARGUMENTS: Dict[VLM, List[str]] = { VLM.QWEN_2_5_VL: ["input_wh", "resolution_wh", "classes"], VLM.GOOGLE_GEMINI_2_0: ["resolution_wh", "classes"], VLM.GOOGLE_GEMINI_2_5: ["resolution_wh", "classes"], + VLM.MOONDREAM: ["resolution_wh"], } SUPPORTED_TASKS_FLORENCE_2 = [ From 2b052430bd562b79934d43dd971a74a076e6af67 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Mon, 14 Jul 2025 19:32:14 +0200 Subject: [PATCH 25/29] updated `from_lmm` docs --- supervision/detection/core.py | 150 +++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 0ab7f03b..0b8c48d2 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -815,6 +815,15 @@ class Detections: """ Creates a Detections object from the given result string based on the specified Large Multimodal Model (LMM). + + | Name | Enum (sv.LMM) | Tasks | Required parameters | Optional parameters | + |---------------------|----------------------|-------------------------|-----------------------------|---------------------| + | PaliGemma | `PALIGEMMA` | detection | `resolution_wh` | `classes` | + | PaliGemma 2 | `PALIGEMMA` | detection | `resolution_wh` | `classes` | + | Qwen2.5-VL | `QWEN_2_5_VL` | detection | `resolution_wh`, `input_wh` | `classes` | + | Google Gemini 2.0 | `GOOGLE_GEMINI_2_0` | detection | `resolution_wh` | `classes` | + | Google Gemini 2.5 | `GOOGLE_GEMINI_2_5` | detection, segmentation | `resolution_wh` | `classes` | + | Moondream | `MOONDREAM` | detection | `resolution_wh` | | Args: lmm (Union[LMM, str]): The type of LMM (Large Multimodal Model) to use. @@ -828,9 +837,10 @@ class Detections: ValueError: If the LMM is invalid, required arguments are missing, or disallowed arguments are provided. ValueError: If the specified LMM is not supported. - - Examples: + + !!! example "PaliGemma" ```python + import supervision as sv paligemma_result = " cat" @@ -850,7 +860,7 @@ class Detections: # {'class_name': array(['cat'], dtype=' Date: Mon, 14 Jul 2025 17:32:49 +0000 Subject: [PATCH 26/29] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 0b8c48d2..6d682b46 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -815,7 +815,7 @@ class Detections: """ Creates a Detections object from the given result string based on the specified Large Multimodal Model (LMM). - + | Name | Enum (sv.LMM) | Tasks | Required parameters | Optional parameters | |---------------------|----------------------|-------------------------|-----------------------------|---------------------| | PaliGemma | `PALIGEMMA` | detection | `resolution_wh` | `classes` | @@ -837,10 +837,10 @@ class Detections: ValueError: If the LMM is invalid, required arguments are missing, or disallowed arguments are provided. ValueError: If the specified LMM is not supported. - + !!! example "PaliGemma" ```python - + import supervision as sv paligemma_result = " cat" @@ -919,35 +919,35 @@ class Detections: ``` !!! example "Gemini 2.5" - + ??? tip "Prompt engineering" - + To get the best results from Google Gemini 2.5, use the following prompt. - - This prompt is designed to detect all visible objects in the image, - including small, distant, or partially visible ones, and to return + + This prompt is designed to detect all visible objects in the image, + including small, distant, or partially visible ones, and to return tight bounding boxes. - + ``` - Carefully examine this image and detect ALL visible objects, including + Carefully examine this image and detect ALL visible objects, including small, distant, or partially visible ones. - - IMPORTANT: Focus on finding as many objects as possible, even if you are + + IMPORTANT: Focus on finding as many objects as possible, even if you are only moderately confident. - + Make sure each bounding box is as tight as possible. - + Valid object classes: {class_list} - + For each detected object, provide: - "label": the exact class name from the list above - "confidence": your certainty (between 0.0 and 1.0) - "box_2d": the bounding box [ymin, xmin, ymax, xmax] normalized to 0–1000 - "mask": the binary mask of the object as a base64-encoded string - - Detect everything that matches the valid classes. Do not be + + Detect everything that matches the valid classes. Do not be conservative; include objects even with moderate confidence. - + Return a JSON array, for example: [ { @@ -964,10 +964,10 @@ class Detections: } ] ``` - - When using the google-genai library, it is recommended to set + + When using the google-genai library, it is recommended to set thinking_budget=0 in thinking_config for more direct and faster responses. - + ```python from google.generativeai import types @@ -980,15 +980,15 @@ class Detections: ) ) ``` - + For a shorter prompt focused only on segmentation masks, you can use: - + ``` - Return a JSON list of segmentation masks. Each entry should include the - 2D bounding box in the "box_2d" key, the segmentation mask in the "mask" + Return a JSON list of segmentation masks. Each entry should include the + 2D bounding box in the "box_2d" key, the segmentation mask in the "mask" key, and the text label in the "label" key. Use descriptive labels. ``` - + ```python import supervision as sv From 85ec990aa455be435de5de76564ae04222661969 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 20:48:35 +0300 Subject: [PATCH 27/29] =?UTF-8?q?docs:=20=F0=9F=93=9D=20improve=20document?= =?UTF-8?q?ation=20for=20VLM=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- supervision/detection/core.py | 139 +++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 61 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 6d682b46..6edd8073 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1037,7 +1037,7 @@ class Detections: ] } - detections = sv.Detections.from_vmm( + detections = sv.Detections.from_lmm( sv.LMM.MOONDREAM, moondream_result, resolution_wh=(1000, 1000), @@ -1084,11 +1084,21 @@ class Detections: cls, vlm: Union[VLM, str], result: Union[str, dict], **kwargs: Any ) -> Detections: """ + Creates a Detections object from the given result string based on the specified Vision Language Model (VLM). + | Name | Enum (sv.VLM) | Tasks | Required parameters | Optional parameters | + |---------------------|----------------------|-------------------------|-----------------------------|---------------------| + | PaliGemma | `PALIGEMMA` | detection | `resolution_wh` | `classes` | + | PaliGemma 2 | `PALIGEMMA` | detection | `resolution_wh` | `classes` | + | Qwen2.5-VL | `QWEN_2_5_VL` | detection | `resolution_wh`, `input_wh` | `classes` | + | Google Gemini 2.0 | `GOOGLE_GEMINI_2_0` | detection | `resolution_wh` | `classes` | + | Google Gemini 2.5 | `GOOGLE_GEMINI_2_5` | detection, segmentation | `resolution_wh` | `classes` | + | Moondream | `MOONDREAM` | detection | `resolution_wh` | | + Args: - vlm (Union[VLM, str]): The type of VLM (Large Multimodal Model) to use. + vlm (Union[VLM, str]): The type of VLM (Vision Language Model) to use. result (str): The result string containing the detection data. **kwargs (Any): Additional keyword arguments required by the specified VLM. @@ -1100,8 +1110,9 @@ class Detections: disallowed arguments are provided. ValueError: If the specified VLM is not supported. - Examples: + !!! example "PaliGemma" ```python + import supervision as sv paligemma_result = " cat" @@ -1121,7 +1132,7 @@ class Detections: # {'class_name': array(['cat'], dtype=' Date: Mon, 14 Jul 2025 20:50:54 +0300 Subject: [PATCH 28/29] =?UTF-8?q?chore:=20=F0=9F=A7=B9formatting=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 6edd8073..ed3cdf15 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -942,7 +942,7 @@ class Detections: For each detected object, provide: - "label": the exact class name from the list above - "confidence": your certainty (between 0.0 and 1.0) - - "box_2d": the bounding box [ymin, xmin, ymax, xmax] normalized to 0–1000 + - "box_2d": the bounding box [ymin, xmin, ymax, xmax] normalized to 0-1000 - "mask": the binary mask of the object as a base64-encoded string Detect everything that matches the valid classes. Do not be @@ -1214,7 +1214,7 @@ class Detections: For each detected object, provide: - "label": the exact class name from the list above - "confidence": your certainty (between 0.0 and 1.0) - - "box_2d": the bounding box [ymin, xmin, ymax, xmax] normalized to 0–1000 + - "box_2d": the bounding box [ymin, xmin, ymax, xmax] normalized to 0-1000 - "mask": the binary mask of the object as a base64-encoded string Detect everything that matches the valid classes. Do not be From efe59cb91c665004247d2d326c0e1895984cd0f4 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 14 Jul 2025 20:57:07 +0300 Subject: [PATCH 29/29] =?UTF-8?q?chore:=20=F0=9F=A7=B9=20use=20same=20var?= =?UTF-8?q?=20name=20compare=20to=20other=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/vlm.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/supervision/detection/vlm.py b/supervision/detection/vlm.py index 664bb6f7..030db73e 100644 --- a/supervision/detection/vlm.py +++ b/supervision/detection/vlm.py @@ -389,14 +389,15 @@ def from_google_gemini_2_0( return np.empty((0, 4)), None, np.empty((0,), dtype=str) labels = [] - xyxy = [] + boxes_list = [] + for item in data: if "box_2d" not in item or "label" not in item: continue labels.append(item["label"]) box = item["box_2d"] # Gemini bbox order is [y_min, x_min, y_max, x_max] - xyxy.append( + boxes_list.append( denormalize_boxes( np.array([box[1], box[0], box[3], box[2]]).astype(np.float64), resolution_wh=(w, h), @@ -404,10 +405,10 @@ def from_google_gemini_2_0( ) ) - if not xyxy: + if not boxes_list: return np.empty((0, 4)), None, np.empty((0,), dtype=str) - xyxy = np.array(xyxy) + xyxy = np.array(boxes_list) class_name = np.array(labels) class_id = None @@ -487,7 +488,7 @@ def from_google_gemini_2_5( None, ) - xyxy_list: list = [] + boxes_list: list = [] labels_list: list = [] confidence_list: Optional[list] = [] masks_list: Optional[list] = [] @@ -503,7 +504,7 @@ def from_google_gemini_2_5( resolution_wh=(w, h), normalization_factor=1000, ) - xyxy_list.append(absolute_bbox) + boxes_list.append(absolute_bbox) if "mask" in item: if masks_list is not None: @@ -540,7 +541,7 @@ def from_google_gemini_2_5( else: confidence_list = None - if not xyxy_list: + if not boxes_list: return ( np.empty((0, 4)), np.array([], dtype=int), @@ -549,7 +550,7 @@ def from_google_gemini_2_5( None, ) - xyxy = np.array(xyxy_list, dtype=float) + xyxy = np.array(boxes_list, dtype=float) class_name = np.array(labels_list) class_id: np.ndarray