Files
xiaden 315666c7b6 refactor: restructure into subpackages, add _shared layer, remove AI slop
- Split flat node modules into categorized subpackages (tagging, detect,
  edge, pose, segment, restore, compare, judge, transform, utility)
- Extract shared internals into _shared/ (bases, tensor, formatting)
- Centralize node registration via register_nodes() helper
- Clean up: remove dead is_scored return, unnecessary hasattr guard,
  inline imports
2026-07-05 08:59:48 +00:00

115 lines
4.1 KiB
Python

"""Object detection for anime images — faces, hands, persons, nudity, text, and YOLO-tagged objects."""
import importlib
import json
from collections import Counter
from comfy_api.latest import io
from .._shared.tensor import comfy_to_pil
from .._shared.formatting import label_display
class ImgUtilsDetect(io.ComfyNode):
"""Detect faces, hands, bodies, nudity, text, and Booru objects in anime images."""
DROPDOWN_OPTIONS = [
"Detect Faces",
"Detect Hands",
"Detect Heads",
"Detect Eyes",
"Detect Person",
"Detect Halfbody",
"Detect with NudeNet",
"Detect Censors",
"Detect Text with OCR",
"Detect with Booru YOLO",
]
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="ImgUtilsDetect",
display_name="Imgutils Detect",
category="imgutils/detect",
description=(
"Detect faces, hands, heads, eyes, persons, half-bodies, "
"text regions, nude areas, censors, and Booru-tagged objects in anime images."
),
search_aliases=[
"detect", "detection", "face", "hand", "head", "eye",
"person", "nudenet", "censor", "booru", "yolo", "bbox",
"text", "ocr",
],
inputs=[
io.Image.Input("image", tooltip="Input image for object detection."),
io.Combo.Input(
"mode",
options=cls.DROPDOWN_OPTIONS,
default="Detect Faces",
tooltip="What to detect — faces, hands, heads, eyes, persons, nudity, censors, text, or Booru YOLO objects.",
),
io.Float.Input(
"confidence",
default=0.5,
min=0.0,
max=1.0,
step=0.05,
tooltip="Minimum confidence threshold. Detections below this are filtered out.",
),
],
outputs=[
io.String.Output(display_name="detections"),
io.String.Output(display_name="json"),
],
)
@classmethod
def execute(cls, image, mode, confidence) -> io.NodeOutput:
pil_image = comfy_to_pil(image)
results = _run_detection(pil_image, mode, confidence)
if not results:
return io.NodeOutput(
f"No detections at confidence >= {confidence:.2f}",
"[]",
)
counts = Counter(label for bbox, label, conf in results)
detections_str = ", ".join(
f"{label_display(label)}: {count}" for label, count in sorted(counts.items())
)
bboxes_json = json.dumps([
{"bbox": [bbox[0], bbox[1], bbox[2], bbox[3]], "label": label_display(label), "score": conf}
for bbox, label, conf in results
], ensure_ascii=False)
return io.NodeOutput(detections_str, bboxes_json)
def _run_detection(pil_image, mode: str, confidence: float) -> list[tuple]:
"""Run a detection function and return raw results."""
detect_map = {
"Detect Faces": "imgutils.detect.detect_faces",
"Detect Hands": "imgutils.detect.detect_hands",
"Detect Heads": "imgutils.detect.detect_heads",
"Detect Eyes": "imgutils.detect.detect_eyes",
"Detect Person": "imgutils.detect.detect_person",
"Detect Halfbody": "imgutils.detect.detect_halfbody",
"Detect with NudeNet": "imgutils.detect.detect_with_nudenet",
"Detect Censors": "imgutils.detect.detect_censors",
"Detect Text with OCR": "imgutils.ocr.detect_text_with_ocr",
"Detect with Booru YOLO": "imgutils.detect.detect_with_booru_yolo",
}
module_name, func_name = detect_map[mode].rsplit(".", 1)
mod = importlib.import_module(module_name)
raw = getattr(mod, func_name)(pil_image)
filtered = []
for item in raw:
bbox, label, conf = item
if conf >= confidence:
filtered.append(item)
return filtered