New Features: - SID_PromptCompose node with Standard and Enhance with AI modes - SID_PromptSynthesis node for VLM-based prompt generation - SID_ImageAnalysis node for comprehensive image analysis - Text LLM support (Qwen2.5 0.5B-7B) for prompt enhancement - PixAI tagger integration Documentation: - docs/image_tokenization_spec.md - Full technical specification for tokenization → classification → assembly pipeline - docs/implementation_plan.md - 8-phase implementation roadmap - docs/SID_PromptCompose.md - Node usage documentation Infrastructure: - core/compose/ module with base, pipeline, standard, llm generators - core/log.py centralized logging - Config files for Qwen2.5 text models (YAML) - Web assets for ComfyUI integration This commit establishes the foundation for rewriting the compose pipeline based on the tokenization specification. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
197 lines
5.9 KiB
Python
197 lines
5.9 KiB
Python
"""
|
|
Debug Analyzer - Uses Claude to identify gaps between source image and generated prompt.
|
|
|
|
Only runs when DEBUG_ENABLED=true and ANTHROPIC_API_KEY is set in .env file.
|
|
If not configured, gracefully skips without errors.
|
|
"""
|
|
|
|
import os
|
|
import base64
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from PIL import Image
|
|
import io
|
|
|
|
|
|
def _load_env():
|
|
"""Load .env file from toolkit root directory."""
|
|
env_path = Path(__file__).parent.parent / ".env"
|
|
if not env_path.exists():
|
|
return {}
|
|
|
|
env_vars = {}
|
|
try:
|
|
with open(env_path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if "=" in line:
|
|
key, value = line.split("=", 1)
|
|
env_vars[key.strip()] = value.strip()
|
|
except Exception:
|
|
pass
|
|
|
|
return env_vars
|
|
|
|
|
|
def _is_debug_enabled() -> bool:
|
|
"""Check if debug mode is enabled."""
|
|
env = _load_env()
|
|
debug_enabled = env.get("DEBUG_ENABLED", "").lower()
|
|
api_key = env.get("ANTHROPIC_API_KEY", "")
|
|
|
|
# Both must be present and debug must be "true"
|
|
return debug_enabled == "true" and api_key and api_key.startswith("sk-ant-")
|
|
|
|
|
|
def _get_api_key() -> Optional[str]:
|
|
"""Get Anthropic API key from .env."""
|
|
env = _load_env()
|
|
return env.get("ANTHROPIC_API_KEY")
|
|
|
|
|
|
def _image_to_base64(image: Image.Image, max_size: int = 1024) -> str:
|
|
"""Convert PIL Image to base64 string, resizing if needed."""
|
|
# Resize if too large
|
|
if image.width > max_size or image.height > max_size:
|
|
ratio = min(max_size / image.width, max_size / image.height)
|
|
new_size = (int(image.width * ratio), int(image.height * ratio))
|
|
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
|
|
|
# Convert to RGB if needed
|
|
if image.mode != "RGB":
|
|
image = image.convert("RGB")
|
|
|
|
# Save to bytes
|
|
buffer = io.BytesIO()
|
|
image.save(buffer, format="JPEG", quality=85)
|
|
buffer.seek(0)
|
|
|
|
return base64.standard_b64encode(buffer.read()).decode("utf-8")
|
|
|
|
|
|
def analyze_prompt_gaps(
|
|
source_image: Image.Image,
|
|
generated_prompt: str,
|
|
metadata: Optional[dict] = None,
|
|
verbose: bool = False,
|
|
) -> Optional[dict]:
|
|
"""
|
|
Use Claude to analyze gaps between source image and generated prompt.
|
|
|
|
Args:
|
|
source_image: The original image that was analyzed
|
|
generated_prompt: The prompt generated by the synthesis node
|
|
metadata: Optional metadata from image analysis stage
|
|
verbose: Print debug info
|
|
|
|
Returns:
|
|
Dict with analysis results, or None if debug not enabled/configured
|
|
"""
|
|
if not _is_debug_enabled():
|
|
if verbose:
|
|
print("[Debug] Debug mode not enabled or API key not configured")
|
|
return None
|
|
|
|
api_key = _get_api_key()
|
|
if not api_key:
|
|
return None
|
|
|
|
try:
|
|
import anthropic
|
|
except ImportError:
|
|
if verbose:
|
|
print("[Debug] anthropic package not installed. Run: pip install anthropic")
|
|
return None
|
|
|
|
if verbose:
|
|
print("[Debug] Running Claude analysis...")
|
|
|
|
# Convert image to base64
|
|
image_b64 = _image_to_base64(source_image)
|
|
|
|
# Build analysis prompt
|
|
analysis_prompt = f"""Analyze this image and compare it to the generated prompt below.
|
|
Identify any gaps, inaccuracies, or missing elements.
|
|
|
|
GENERATED PROMPT:
|
|
{generated_prompt}
|
|
|
|
Please analyze and provide:
|
|
|
|
1. **INACCURACIES** - Things in the prompt that are WRONG (e.g., wrong colors, wrong garment types, wrong positions)
|
|
|
|
2. **MISSING ELEMENTS** - Important visible elements NOT mentioned in the prompt (e.g., exposed skin areas, accessories, headwear layers)
|
|
|
|
3. **HALLUCINATIONS** - Things mentioned in the prompt that are NOT visible in the image (e.g., "hands in pockets" when hands are visible elsewhere)
|
|
|
|
4. **COLOR ERRORS** - Any color descriptions that don't match (e.g., "white" when it's actually "gray")
|
|
|
|
5. **SUGGESTED CORRECTIONS** - Brief corrected descriptions for the main issues
|
|
|
|
Be specific and concise. Focus on the most important gaps that would affect image regeneration accuracy."""
|
|
|
|
try:
|
|
client = anthropic.Anthropic(api_key=api_key)
|
|
|
|
message = client.messages.create(
|
|
model="claude-sonnet-4-20250514",
|
|
max_tokens=1500,
|
|
messages=[
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "image",
|
|
"source": {
|
|
"type": "base64",
|
|
"media_type": "image/jpeg",
|
|
"data": image_b64,
|
|
},
|
|
},
|
|
{
|
|
"type": "text",
|
|
"text": analysis_prompt,
|
|
},
|
|
],
|
|
}
|
|
],
|
|
)
|
|
|
|
analysis_text = message.content[0].text
|
|
|
|
if verbose:
|
|
print(f"[Debug] Claude analysis complete ({len(analysis_text)} chars)")
|
|
|
|
return {
|
|
"analysis": analysis_text,
|
|
"model": "claude-sonnet-4-20250514",
|
|
"prompt_length": len(generated_prompt),
|
|
}
|
|
|
|
except anthropic.APIError as e:
|
|
if verbose:
|
|
print(f"[Debug] Anthropic API error: {e}")
|
|
return {"error": str(e)}
|
|
except Exception as e:
|
|
if verbose:
|
|
print(f"[Debug] Analysis error: {e}")
|
|
return {"error": str(e)}
|
|
|
|
|
|
def print_analysis(analysis: Optional[dict]) -> None:
|
|
"""Pretty print the analysis results."""
|
|
if not analysis:
|
|
return
|
|
|
|
if "error" in analysis:
|
|
print(f"\n[Debug Analysis Error] {analysis['error']}")
|
|
return
|
|
|
|
print("\n" + "=" * 60)
|
|
print("DEBUG ANALYSIS - Gaps in Generated Prompt")
|
|
print("=" * 60)
|
|
print(analysis.get("analysis", "No analysis available"))
|
|
print("=" * 60 + "\n")
|