mirror of
https://github.com/vladmandic/automatic
synced 2026-08-26 15:16:01 +02:00
refactor(api): update cli tools for DeepBooru tagger migration
- Update cli/api-interrogate.py to use /sdapi/v1/tagger for DeepBooru - Handle tagger response format (scores dict or tags string) - Remove DeepBooru test from interrogate endpoint tests - Update API model descriptions to reference tagger for anime tagging
This commit is contained in:
+14
-14
@@ -9,6 +9,7 @@ import sys
|
||||
import os
|
||||
import asyncio
|
||||
import filetype
|
||||
from types import SimpleNamespace
|
||||
from PIL import Image
|
||||
from util import log, Map
|
||||
import sdapi
|
||||
@@ -65,22 +66,21 @@ async def interrogate(f):
|
||||
stats['captions'][word] = stats['captions'][word] + 1 if word in stats['captions'] else 1
|
||||
else:
|
||||
log.error({ 'interrogate clip error': res })
|
||||
# run booru
|
||||
json.model = 'deepdanbooru'
|
||||
res = await sdapi.post('/sdapi/v1/interrogate', json)
|
||||
# run tagger (DeepBooru)
|
||||
tagger_req = SimpleNamespace(image=json.image, model='deepbooru', show_scores=True)
|
||||
res = await sdapi.post('/sdapi/v1/tagger', tagger_req)
|
||||
keywords = {}
|
||||
if 'caption' in res:
|
||||
for term in res.caption.split(', '):
|
||||
term = term.replace('(', '').replace(')', '').replace('\\', '').split(':')
|
||||
if len(term) < 2:
|
||||
continue
|
||||
keywords[term[0]] = term[1]
|
||||
keywords = dict(sorted(keywords.items(), key=lambda x:x[1], reverse=True))
|
||||
for word in keywords.items():
|
||||
stats['keywords'][word[0]] = stats['keywords'][word[0]] + 1 if word[0] in stats['keywords'] else 1
|
||||
log.info({ 'interrogate keywords': keywords })
|
||||
if 'scores' in res and res.scores:
|
||||
keywords = dict(sorted(res.scores.items(), key=lambda x: x[1], reverse=True))
|
||||
for word in keywords:
|
||||
stats['keywords'][word] = stats['keywords'][word] + 1 if word in stats['keywords'] else 1
|
||||
log.info({'interrogate keywords': keywords})
|
||||
elif 'tags' in res:
|
||||
for tag in res.tags.split(', '):
|
||||
stats['keywords'][tag] = stats['keywords'][tag] + 1 if tag in stats['keywords'] else 1
|
||||
log.info({'interrogate tags': res.tags})
|
||||
else:
|
||||
log.error({ 'interrogate booru error': res })
|
||||
log.error({'interrogate tagger error': res})
|
||||
return caption, keywords, style
|
||||
|
||||
|
||||
|
||||
+2
-39
@@ -3,7 +3,7 @@
|
||||
Caption API Test Suite
|
||||
|
||||
Comprehensive tests for all Caption API endpoints and parameters:
|
||||
- GET/POST /sdapi/v1/interrogate (OpenCLiP/DeepBooru)
|
||||
- GET/POST /sdapi/v1/interrogate (OpenCLiP)
|
||||
- POST /sdapi/v1/vqa (VLM Captioning with annotated images)
|
||||
- GET /sdapi/v1/vqa/models, /sdapi/v1/vqa/prompts
|
||||
- POST /sdapi/v1/tagger
|
||||
@@ -302,13 +302,7 @@ class CaptionAPITest:
|
||||
self.log_fail(f"Expected list, got {type(data)}")
|
||||
return
|
||||
|
||||
# Test 2: Contains deepdanbooru
|
||||
if 'deepdanbooru' in data:
|
||||
self.log_pass("Contains 'deepdanbooru'")
|
||||
else:
|
||||
self.log_fail("Missing 'deepdanbooru'")
|
||||
|
||||
# Test 3: Contains OpenCLIP models (format: arch/dataset)
|
||||
# Test 2: Contains OpenCLIP models (format: arch/dataset)
|
||||
clip_models = [m for m in data if '/' in m]
|
||||
if clip_models:
|
||||
self.log_pass(f"Contains {len(clip_models)} OpenCLIP models")
|
||||
@@ -316,36 +310,6 @@ class CaptionAPITest:
|
||||
else:
|
||||
self.log_skip("No OpenCLIP models found (may need to download)")
|
||||
|
||||
# =========================================================================
|
||||
# TEST: POST /sdapi/v1/interrogate - DeepBooru
|
||||
# =========================================================================
|
||||
def test_interrogate_post_deepbooru(self):
|
||||
"""Test DeepBooru interrogation."""
|
||||
print("\n" + "=" * 70)
|
||||
print("TEST: POST /sdapi/v1/interrogate (DeepBooru)")
|
||||
print("=" * 70)
|
||||
|
||||
t0 = time.time()
|
||||
data = self.post('/sdapi/v1/interrogate', {
|
||||
'image': self.image_b64,
|
||||
'model': 'deepdanbooru'
|
||||
})
|
||||
elapsed = time.time() - t0
|
||||
|
||||
if 'error' in data:
|
||||
self.log_skip(f"DeepBooru: {data.get('reason', 'failed')} (model may not be loaded)")
|
||||
return
|
||||
|
||||
caption = data.get('caption', '')
|
||||
if caption and not self.is_error_answer(caption):
|
||||
caption_preview = caption[:80] + '...' if len(caption) > 80 else caption
|
||||
self.log_pass(f"DeepBooru returns caption ({elapsed:.1f}s)")
|
||||
self.log_info(f"Caption: {caption_preview}")
|
||||
elif self.is_error_answer(caption):
|
||||
self.log_fail(f"DeepBooru returned error: {caption}")
|
||||
else:
|
||||
self.log_fail("DeepBooru returned empty caption")
|
||||
|
||||
# =========================================================================
|
||||
# TEST: POST /sdapi/v1/interrogate - OpenCLIP Modes
|
||||
# =========================================================================
|
||||
@@ -2323,7 +2287,6 @@ class CaptionAPITest:
|
||||
|
||||
# Interrogate tests
|
||||
self.test_interrogate_list_models()
|
||||
self.test_interrogate_post_deepbooru()
|
||||
self.test_interrogate_post_clip_modes()
|
||||
self.test_interrogate_analyze()
|
||||
self.test_interrogate_invalid_inputs()
|
||||
|
||||
@@ -98,16 +98,17 @@ def get_interrogate():
|
||||
Returns model identifiers for use with POST /sdapi/v1/interrogate.
|
||||
|
||||
**Model Types:**
|
||||
- `deepdanbooru`: Anime-style image tagger returning comma-separated tags
|
||||
- OpenCLIP models: Format `architecture/pretrained_dataset` (e.g., `ViT-L-14/openai`)
|
||||
|
||||
For anime-style tagging (WaifuDiffusion, DeepBooru), use `/sdapi/v1/tagger` instead.
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
["deepdanbooru", "ViT-L-14/openai", "ViT-H-14/laion2b_s32b_b79k"]
|
||||
["ViT-L-14/openai", "ViT-H-14/laion2b_s32b_b79k"]
|
||||
```
|
||||
"""
|
||||
from modules.interrogate.openclip import refresh_clip_models
|
||||
return ['deepdanbooru'] + refresh_clip_models()
|
||||
return refresh_clip_models()
|
||||
|
||||
def get_schedulers():
|
||||
from modules.sd_samplers import list_samplers
|
||||
|
||||
@@ -368,11 +368,11 @@ class ResStatus(BaseModel):
|
||||
class ReqInterrogate(BaseModel):
|
||||
"""Request model for OpenCLIP/BLIP image interrogation.
|
||||
|
||||
Analyze image using CLIP model via OpenCLIP to generate prompts,
|
||||
or use DeepDanbooru for anime-style tagging.
|
||||
Analyze image using CLIP model via OpenCLIP to generate prompts.
|
||||
For anime-style tagging, use /sdapi/v1/tagger with WaifuDiffusion or DeepBooru.
|
||||
"""
|
||||
image: str = Field(default="", title="Image", description="Image to interrogate. Must be a Base64 encoded string containing the image data (PNG/JPEG).")
|
||||
model: str = Field(default="ViT-L-14/openai", title="Model", description="OpenCLIP model to use. Use 'deepdanbooru' or 'deepbooru' for anime tagging. Get available models from GET /sdapi/v1/interrogate.")
|
||||
model: str = Field(default="ViT-L-14/openai", title="Model", description="OpenCLIP model to use. Get available models from GET /sdapi/v1/interrogate.")
|
||||
clip_model: str = Field(default="ViT-L-14/openai", title="CLIP Model", description="CLIP model used for image-text similarity matching. Larger models (ViT-L, ViT-H) are more accurate but slower and use more VRAM.")
|
||||
blip_model: str = Field(default="blip-large", title="Caption Model", description="BLIP model used to generate the initial image caption. The caption model describes the image content which CLIP then enriches with style and flavor terms.")
|
||||
mode: str = Field(default="best", title="Mode", description="Interrogation mode. Fast: Quick caption with minimal flavor terms. Classic: Standard interrogation with balanced quality and speed. Best: Most thorough analysis, slowest but highest quality. Negative: Generate terms to use as negative prompt.")
|
||||
|
||||
Reference in New Issue
Block a user