Files
daminik124124-ops-ComfyUI-S…/json_regions_to_sam3_boxes_api.py

136 lines
4.4 KiB
Python

import json
class JSONRegionsToSAM3BoxesAPI:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"json_text": ("STRING", {
"multiline": True,
"default": '{\n "regions": [\n { "x1": 78, "y1": 0, "width": 234, "height": 119 }\n ]\n}'
}),
"padding": ("INT", {"default": 0, "min": 0, "max": 512, "step": 1}),
}
}
RETURN_TYPES = ("SAM3_BOXES_PROMPT", "SAM3_BOXES_PROMPT", "STRING")
RETURN_NAMES = ("positive_boxes", "negative_boxes", "debug_info")
FUNCTION = "run"
CATEGORY = "SAM3/prompts"
def _extract_regions(self, data):
if isinstance(data, list):
return data, []
if not isinstance(data, dict):
raise ValueError("JSON must be an object or a list of region objects.")
positive = (
data.get("positive_regions")
or data.get("regions")
or data.get("boxes")
or data.get("positive_boxes")
or []
)
negative = data.get("negative_regions") or data.get("negative_boxes") or []
return positive, negative
def _region_to_xyxy(self, r, idx):
if isinstance(r, dict):
if all(k in r for k in ("x1", "y1", "x2", "y2")):
return float(r["x1"]), float(r["y1"]), float(r["x2"]), float(r["y2"])
if all(k in r for k in ("x1", "y1", "width", "height")):
x1 = float(r["x1"])
y1 = float(r["y1"])
return x1, y1, x1 + float(r["width"]), y1 + float(r["height"])
if all(k in r for k in ("x", "y", "w", "h")):
x1 = float(r["x"])
y1 = float(r["y"])
return x1, y1, x1 + float(r["w"]), y1 + float(r["h"])
if all(k in r for k in ("x", "y", "width", "height")):
x1 = float(r["x"])
y1 = float(r["y"])
return x1, y1, x1 + float(r["width"]), y1 + float(r["height"])
raise ValueError(
f"Bad region at index {idx}: {r}. "
"Supported: x1/y1/x2/y2, x1/y1/width/height, x/y/w/h, x/y/width/height."
)
if isinstance(r, (list, tuple)) and len(r) >= 4:
return tuple(map(float, r[:4]))
raise ValueError(f"Bad region at index {idx}: {r}")
def _to_prompt(self, regions, label, img_w, img_h, padding):
boxes = []
labels = []
for idx, r in enumerate(regions):
x1, y1, x2, y2 = self._region_to_xyxy(r, idx)
if x2 < x1:
x1, x2 = x2, x1
if y2 < y1:
y1, y2 = y2, y1
x1 = max(0.0, x1 - padding)
y1 = max(0.0, y1 - padding)
x2 = min(float(img_w), x2 + padding)
y2 = min(float(img_h), y2 + padding)
if x2 <= x1 or y2 <= y1:
continue
x1n = x1 / img_w
y1n = y1 / img_h
x2n = x2 / img_w
y2n = y2 / img_h
cx = (x1n + x2n) / 2.0
cy = (y1n + y2n) / 2.0
w = x2n - x1n
h = y2n - y1n
boxes.append([cx, cy, w, h])
labels.append(bool(label))
return {"boxes": boxes, "labels": labels}
def run(self, image, json_text, padding=0):
img_h = int(image.shape[1])
img_w = int(image.shape[2])
if not str(json_text).strip():
raise ValueError("json_text is empty.")
data = json.loads(json_text)
positive_regions, negative_regions = self._extract_regions(data)
positive_prompt = self._to_prompt(positive_regions, True, img_w, img_h, padding)
negative_prompt = self._to_prompt(negative_regions, False, img_w, img_h, padding)
debug = (
f"Mode: API/json_text\n"
f"Image: {img_w}x{img_h}\n"
f"Positive regions: {len(positive_regions)} -> boxes: {len(positive_prompt['boxes'])}\n"
f"Negative regions: {len(negative_regions)} -> boxes: {len(negative_prompt['boxes'])}\n"
f"Padding: {padding}px\n"
f"Supported input: x1/y1/x2/y2, x1/y1/width/height, x/y/w/h"
)
return (positive_prompt, negative_prompt, debug)
NODE_CLASS_MAPPINGS = {
"JSONRegionsToSAM3BoxesAPI": JSONRegionsToSAM3BoxesAPI,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"JSONRegionsToSAM3BoxesAPI": "JSON Regions to SAM3 Boxes API",
}