Files
slahiri-ComfyUI-AI-Photogra…/docs/image_tokenization_spec.md
slahiriandClaude Opus 4.5 7f1186d412 feat: add prompt composition pipeline and tokenization spec
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>
2025-12-27 02:33:17 +05:30

109 KiB
Raw Permalink Blame History

Image Tokenization & Classification Specification

Overview

This document defines the approach for extracting, normalizing, classifying, and validating image metadata tokens for prompt composition.

Version: 1.1 Status: Design Specification Date: December 2024 Last Updated: Added Phase 7 (Token Grounding Visualization)


Document Description
Implementation Plan Step-by-step implementation roadmap with tasks and priorities
CLAUDE.md Project overview and architecture

Context Recovery

If resuming after context loss:

  1. Read this spec for technical details
  2. Read implementation_plan.md for current progress and next steps
  3. Check task checkboxes in implementation plan to see what's done

Table of Contents

  1. Phase 1: Tokenization
  2. Phase 2: Normalization
  3. Phase 3: Classification
  4. Phase 4: Assembly
  5. Phase 5: Assembly (Rule-Based)
  6. Phase 6: LLM-Based Assembly
  7. Phase 7: Token Grounding Visualization
  8. Validation & Quality Analysis

Phase 1: Tokenization

1.1 Token Structure

ImageToken {
    text: string              # The actual content
    confidence: float         # 0.0-1.0 confidence score
    source: string            # wd14, pixai, joytag, florence_caption, etc.
    token_type: enum          # TAG | PHRASE | KEY_VALUE | SENTENCE
    metadata: object          # additional context
}

1.2 Extraction Strategy by Source Type

A. TAGGER OUTPUTS (wd14, pixai, joytag, nudenet)

Input:

"wd14": {
    "1girl": 0.999,
    "solo": 0.978,
    "realistic": 0.975
}

Extraction:

  • Direct key-value extraction
  • Key → text, Value → confidence
  • Token type: TAG

Output:

ImageToken(text="1girl", confidence=0.999, source="wd14", token_type=TAG)
ImageToken(text="solo", confidence=0.978, source="wd14", token_type=TAG)

B. ANALYZER OUTPUTS (photography, iqa, composition, saliency)

Input:

"photography": {
    "light gray tones": 0.9,
    "high key": 0.8,
    "portrait orientation": 0.95
}

Extraction:

  • Same as taggers but mark source_type as ANALYZER
  • These often have implicit category hints (photography → Lighting/Style)

C. VLM CAPTIONS (florence_caption, florence_description, florence_mixed_caption)

Input:

"Photo of a young woman with long, wavy brown hair, wearing a white Calvin Klein
crop top and matching panties. She has a fair complexion and is standing confidently
with her hands on her hips. The background is plain and light grey, and the lighting
is soft and even."

Extraction Strategy:

Step 1: Sentence Segmentation

  • Split by ., !, ?
  • Handle edge cases (abbreviations, decimals)

Step 2: Clause Extraction

  • Split sentences by ,, and, with, while
  • Preserve meaning units

Step 3: Phrase Pattern Extraction Use regex patterns for common structures:

Pattern Example Extraction
a/an [adj]* [noun] "a young woman" subject phrase
with [noun phrase] "with long, wavy brown hair" attribute
wearing [noun phrase] "wearing a white crop top" clothing
[verb]ing [adverb]* "standing confidently" action
The [noun] is [adj] "The background is plain" environment
[noun] is [adj] and [adj] "lighting is soft and even" lighting

Step 4: Output Multiple Tokens per Caption

ImageToken(text="young woman", token_type=PHRASE, source="florence_caption")
ImageToken(text="long wavy brown hair", token_type=PHRASE, source="florence_caption")
ImageToken(text="wearing white Calvin Klein crop top", token_type=PHRASE, ...)
ImageToken(text="standing confidently", token_type=PHRASE, ...)
ImageToken(text="background plain light grey", token_type=PHRASE, ...)
ImageToken(text="lighting soft and even", token_type=PHRASE, ...)

D. VLM STRUCTURED OUTPUTS (florence_analyze)

Input:

"camera_angle: front view, art_style: photo realistic, location: indoor,
background: plain light grey background, clothing: white crop top,
action: standing, facial_expression: neutral, hair_color: dark brown hair"

Extraction:

  • Parse key-value pairs by : and ,/;
  • Preserve the key as metadata (helps classification)
  • Token type: KEY_VALUE

Output:

ImageToken(text="front view", key="camera_angle", token_type=KEY_VALUE, ...)
ImageToken(text="photo realistic", key="art_style", token_type=KEY_VALUE, ...)
ImageToken(text="indoor", key="location", token_type=KEY_VALUE, ...)

E. MIXED TAG SECTIONS (from florence_mixed_caption_plus)

Input:

"1girl, solo, long hair, breasts, looking at viewer, large breasts,
brown hair (long hair), black hair, navel, cleavage, brown eyes, standing"

Extraction:

  • Split by ,
  • Strip whitespace
  • Handle parenthetical qualifiers: brown hair (long hair) → keep as single token or split
  • Token type: TAG
  • Confidence: 1.0 (VLM generated, no score)

1.3 Phrase Decomposition Algorithm for Natural Text

INPUT: "a woman with a fair complexion, dark brown hair, and a confident expression,
        standing with her arms at her sides, looking directly at the camera"

STEP 1: Identify Main Subject
        → "a woman" [SUBJECT]

STEP 2: Extract "with" clauses
        → "fair complexion" [SUBJECT_DETAIL]
        → "dark brown hair" [SUBJECT_DETAIL]
        → "confident expression" [SUBJECT_DETAIL]
        → "arms at her sides" [ACTION_POSE]

STEP 3: Extract "-ing" verb phrases
        → "standing with her arms at her sides" [ACTION_POSE]
        → "looking directly at the camera" [ACTION_POSE]

STEP 4: Chunk remaining phrases
        → Individual attribute phrases

Linguistic Chunking Approach:

Use a lightweight NLP chunker (spaCy, Stanza) to identify:

  • NP (Noun Phrases): subjects, objects, attributes
  • VP (Verb Phrases): actions
  • ADJP (Adjective Phrases): qualities
  • PP (Prepositional Phrases): locations, relationships

Phase 2: Normalization

2.1 Text Normalization Pipeline

Raw Token
    │
    ▼
┌─────────────────────────────┐
│  Lowercase                  │
│  "Brown Hair" → "brown hair"│
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  Whitespace Normalization   │
│  "long  hair" → "long hair" │
└─────────────────────────────┘
    │
    ▼
┌─────────────────────────────┐
│  Special Char Handling      │
│  "1girl" → "1girl"          │
│  "calvin klein" → keep as-is│
└─────────────────────────────┘
    │
    ▼
Normalized Token

2.2 Deduplication Strategy

Exact Duplicates:

  • Same normalized text → merge, keep highest confidence

Semantic Duplicates:

  • "1girl" vs "1 girl" vs "one girl" → merge
  • "brown hair" vs "hair brown" → merge
  • Use embedding similarity > 0.95 threshold

Hierarchical Duplicates:

  • "long wavy brown hair" contains "brown hair" and "long hair"
  • Keep the more specific one OR keep both with relationship flag

Confidence Aggregation for Duplicates:

merged_confidence = 1 - ∏(1 - conf_i)  # probability union
OR
merged_confidence = max(conf_i)         # simpler
OR
merged_confidence = weighted_average(conf_i, source_weight_i)

2.3 Source Reliability Weighting

Source Weight Rationale
wd14 1.0 Well-calibrated tagger
joytag 0.95 Good general tagger
pixai 0.9 Slightly less calibrated
florence_caption 0.85 Good but verbose
florence_analyze 0.9 Structured, reliable
nudenet 0.8 Specific domain

Phase 3: Classification

3.1 Classification Pipeline

┌─────────────────────────────────────────────────────────────────┐
│  LAYER 1: Deterministic Rules (Structured Keys)                  │
│                                                                  │
│  If token has key from florence_analyze:                         │
│    camera_angle, distance_to_camera → [Composition]              │
│    art_style → [Style/Medium]                                    │
│    location, background → [Environment/Scene]                    │
│    action, facing_direction → [Action/Pose]                      │
│    clothing, pants, shoes, accessory → [Subject Details]         │
│    hair_*, eye_*, facial_*, body, race → [Subject Details]       │
│    gender → [Subject]                                            │
│    text → [Technical Parameters]                                 │
└─────────────────────────────────────────────────────────────────┘
                               │
                        Not matched
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  LAYER 2: Source-Based Routing                                   │
│                                                                  │
│  If source == "photography":                                     │
│    terms with "light", "tone", "contrast" → [Lighting]           │
│    terms with "sharp", "focus", "quality" → [Quality Boosters]   │
│    terms with "orientation", "frame" → [Composition]             │
│                                                                  │
│  If source == "iqa":                                             │
│    All → [Quality Boosters]                                      │
│                                                                  │
│  If source == "composition" or "saliency":                       │
│    All → [Composition]                                           │
└─────────────────────────────────────────────────────────────────┘
                               │
                        Not matched
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  LAYER 3: Keyword Dictionary Lookup                              │
│                                                                  │
│  For each category, check:                                       │
│    1. Exact match in category.exact_matches                      │
│    2. Prefix/suffix match in category.patterns                   │
│    3. Regex match in category.regex_patterns                     │
│                                                                  │
│  If match found → assign to category                             │
└─────────────────────────────────────────────────────────────────┘
                               │
                        Not matched
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  LAYER 4: Embedding Similarity (Optional)                        │
│                                                                  │
│  Compute embedding of token                                      │
│  Compare to category centroid embeddings                         │
│  Assign to highest similarity category if > threshold            │
└─────────────────────────────────────────────────────────────────┘
                               │
                        Not matched
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│  LAYER 5: Uncategorized Bucket                                   │
│                                                                  │
│  Preserve for manual review or assembly-phase handling           │
└─────────────────────────────────────────────────────────────────┘

3.2 Keyword Dictionary Structure

CategoryDictionary {
    category: string
    exact_matches: Set[string]           # O(1) lookup
    prefix_patterns: List[string]        # "hair_*", "eye_*"
    regex_patterns: List[regex]          # complex patterns
    semantic_anchors: List[string]       # for embedding fallback
    negative_keywords: Set[string]       # explicit exclusions
}

Category Dictionaries:

[Quality Boosters]

exact_matches: {
    "realistic", "photorealistic", "photo realistic", "high quality",
    "masterpiece", "best quality", "ultra detailed", "8k", "4k", "hdr",
    "sharp", "in focus", "detailed", "professional", "excellent quality",
    "good aesthetic quality", "excellent technical quality", "sharp and clean",
    "excellent clarity", "photograph", "photography", "photo medium"
}
semantic_anchors: [
    "high quality professional image",
    "sharp detailed realistic photo",
    "masterpiece best quality"
]

[Subject]

exact_matches: {
    "1girl", "2girls", "3girls", "1boy", "2boys", "solo", "multiple girls",
    "multiple boys", "1woman", "1man", "couple", "group", "crowd",
    "female", "male", "person", "people", "model"
}
prefix_patterns: ["*girl*", "*boy*", "*woman*", "*man*"]
semantic_anchors: [
    "person woman man subject",
    "single solo individual",
    "group multiple people"
]

[Subject Details]

subcategories: {
    "hair": {
        exact: {"long hair", "short hair", "brown hair", "blonde hair", ...}
        prefix: ["hair_*", "*_hair"]
    },
    "eyes": {
        exact: {"brown eyes", "blue eyes", "looking at viewer", ...}
        prefix: ["eye_*", "*_eyes"]
    },
    "face": {
        exact: {"lips", "nose", "parted lips", "smile", ...}
        prefix: ["facial_*"]
    },
    "body": {
        exact: {"breasts", "navel", "slim", "curvy", "toned", ...}
        prefix: ["body_*"]
    },
    "clothing": {
        exact: {"underwear", "panties", "bra", "dress", "shirt", ...}
        prefix: ["clothing_*", "wearing_*"]
    },
    "skin": {
        exact: {"fair complexion", "tan", "pale skin", "dark skin", ...}
        prefix: ["skin_*", "race_*"]
    }
}

[Action/Pose]

exact_matches: {
    "standing", "sitting", "walking", "running", "lying", "kneeling",
    "arms at sides", "hands on hips", "cowboy shot", "looking at viewer",
    "looking away", "from behind", "from side", "facing viewer"
}
verb_patterns: ["*ing", "*_shot"]  # standing, cowboy_shot
semantic_anchors: [
    "person standing sitting posing",
    "action movement pose position",
    "looking facing direction"
]

[Environment/Scene]

exact_matches: {
    "indoor", "outdoor", "background", "simple background", "grey background",
    "white background", "gradient background", "plain background", "studio",
    "bedroom", "street", "forest", "beach", "city"
}
prefix_patterns: ["background_*", "location_*"]
semantic_anchors: [
    "background environment setting location",
    "indoor outdoor scene place"
]

[Lighting]

exact_matches: {
    "soft light", "hard light", "natural light", "studio light", "rim light",
    "backlight", "bright", "dark", "shadows", "high key", "low key",
    "soft and even", "dramatic lighting", "neutral tones", "muted colors",
    "light gray tones", "desaturated", "low contrast", "high contrast"
}
semantic_anchors: [
    "lighting illumination bright dark",
    "soft hard natural studio light",
    "shadows contrast tones"
]

[Style/Medium]

exact_matches: {
    "photo", "photograph", "digital art", "illustration", "3d render",
    "anime", "realistic", "oil painting", "watercolor", "sketch",
    "minimalist", "modern", "vintage", "retro", "artistic"
}
prefix_patterns: ["art_style_*", "style_*"]
semantic_anchors: [
    "art style medium technique",
    "photo realistic illustration painting"
]

[Composition]

exact_matches: {
    "front view", "side view", "portrait", "landscape", "close up",
    "full body", "upper body", "cowboy shot", "dutch angle", "birds eye",
    "centered", "rule of thirds", "symmetry", "portrait orientation",
    "vertical frame", "horizontal frame", "2:3 aspect ratio"
}
prefix_patterns: ["camera_angle_*", "distance_*", "composition_*"]
semantic_anchors: [
    "camera angle view shot framing",
    "composition layout arrangement",
    "full body close up portrait"
]

[Technical Parameters]

exact_matches: {
    "width", "height", "resolution", "aspect ratio", "jpeg artifacts",
    "low resolution", "high resolution", "text", "watermark"
}
prefix_patterns: ["image_info_*", "text_*"]
regex_patterns: [r"\d+x\d+", r"\d+:\d+"]  # dimensions, ratios

3.3 Embedding-Based Classification

Approach: Category Centroid Matching

Step 1: Build Category Embeddings (One-time, Offline)

For each category, create a centroid embedding:

For category in categories:
    anchor_texts = category.semantic_anchors + sample(category.exact_matches, 50)
    embeddings = [embed(text) for text in anchor_texts]
    category_centroid = mean(embeddings)
    category_embeddings[category] = normalize(category_centroid)

Step 2: Runtime Classification

token_embedding = normalize(embed(token.text))

similarities = {}
for category, centroid in category_embeddings:
    similarities[category] = cosine_similarity(token_embedding, centroid)

best_category = argmax(similarities)
confidence = similarities[best_category]

if confidence > THRESHOLD:  # 0.5 recommended
    return (best_category, confidence)
else:
    return (UNKNOWN, confidence)
Model Speed Quality Memory
all-MiniLM-L6-v2 Very Fast Good 80MB
all-mpnet-base-v2 Medium Better 420MB
paraphrase-MiniLM-L6-v2 Very Fast Good for short 80MB

Recommendation: all-MiniLM-L6-v2 for best speed/quality tradeoff


3.4 Multi-Category Token Handling

Some tokens genuinely belong to multiple categories:

Example: "looking at viewer"

  • [Action/Pose] - it's an action
  • [Subject Details] → eyes - eye direction

Strategy: Multi-Label with Primary Flag

TokenClassification {
    token: ImageToken
    primary_category: string
    secondary_categories: List[string]
    confidence_per_category: Dict[string, float]
}

3.5 Caption → Canonical Category Extraction

The Challenge

Caption text interleaves multiple categories in single sentences:

INPUT:
"she has long, wavy brown hair and is wearing a white crop top with
the Calvin Klein logo, standing confidently against a plain grey background"

CONTAINS:
├── [Subject Details/hair]: "long, wavy brown hair"
├── [Subject Details/clothing]: "white crop top with Calvin Klein logo"
├── [Action/Pose]: "standing confidently"
└── [Environment]: "plain grey background"

Strategy: Hierarchical Clause Extraction

┌─────────────────────────────────────────────────────────────────┐
│                     FULL CAPTION                                 │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              LEVEL 1: SENTENCE SEGMENTATION                      │
│                                                                  │
│  Split by: . ! ?                                                │
│  Handle: abbreviations (Dr., Mr.), decimals (0.5)               │
│  Result: List of complete sentences                              │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              LEVEL 2: CLAUSE SEGMENTATION                        │
│                                                                  │
│  Split by: , | and | with | while | as | who | which            │
│  Preserve: quoted text, parenthetical content                   │
│                                                                  │
│  "she has long hair, wearing a dress, standing confidently"     │
│       ↓                                                          │
│  ["she has long hair", "wearing a dress", "standing confidently"]│
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              LEVEL 3: PHRASE PATTERN MATCHING                    │
│                                                                  │
│  Each clause matched against category-specific patterns         │
│  Multiple matches per clause allowed                            │
│  Unmatched portions preserved for fallback                      │
└─────────────────────────────────────────────────────────────────┘

Category-Specific Extraction Patterns

[Subject] Patterns
Trigger Structures:
├── "a/an [adjective]* [ethnicity]? [subject_noun]"
│   Examples: "a young woman", "an attractive asian model"
│
├── "the [subject_noun]"
│   Examples: "the woman", "the model"
│
└── Standalone: "woman", "man", "model", "person", "girl", "boy"

Subject Nouns: woman, man, girl, boy, lady, gentleman, person,
              model, figure, individual, subject

Ethnicity Terms: asian, indian, european, african, latin,
                japanese, korean, chinese, caucasian
[Subject Details] Patterns

Hair:

├── "[length]? [texture]? [color] hair"
│   Examples: "long wavy brown hair", "short blonde hair"
│
├── "hair [is/styled/worn] [description]"
│   Examples: "hair is styled in loose waves"
│
├── "[subject] has [hair description]"
│   Examples: "she has dark curly hair"
│
Length: long, short, medium, shoulder-length
Texture: wavy, curly, straight, frizzy
Color: brown, black, blonde, red, white, gray, dark, light, auburn

Eyes:

├── "[color] eyes"
│   Examples: "brown eyes", "deep blue eyes"
│
├── "eyes are [description]"
│   Examples: "eyes are looking directly at camera"
│
Color: brown, blue, green, hazel, dark, light, amber, gray

Clothing:

├── "wearing [clothing description]"
│   Examples: "wearing a white crop top"
│
├── "dressed in [clothing description]"
│   Examples: "dressed in formal attire"
│
├── "in a [clothing item]"
│   Examples: "in a red dress"
│
├── "[clothing item] with [detail]"
│   Examples: "top with Calvin Klein logo"
│
├── "paired with [clothing]"
│   Examples: "paired with matching briefs"

Clothing Terms: dress, top, shirt, blouse, pants, shorts, skirt,
               bra, panties, underwear, bikini, lingerie, suit,
               jacket, coat, saree, lehenga, kimono, etc.

Body:

├── "[body descriptor] physique/figure/build"
│   Examples: "slim toned physique", "athletic build"
│
├── "[subject] has a [body description]"
│   Examples: "she has a curvy figure"

Body Terms: slim, curvy, athletic, toned, petite, tall,
           muscular, fit, slender

Skin/Complexion:

├── "[skin descriptor] skin/complexion"
│   Examples: "fair complexion", "olive skin"

Skin Terms: fair, pale, tan, olive, dark, brown, warm, smooth,
           light, medium, deep
[Action/Pose] Patterns
├── "[verb-ing] [adverb]? [prepositional phrase]?"
│   Examples: "standing confidently", "sitting on a chair"
│
├── "looking [direction/target]"
│   Examples: "looking at the camera", "looking away"
│
├── "[body part] [position]"
│   Examples: "arms at her sides", "hands on hips"
│
├── "[expression type] expression"
│   Examples: "neutral expression", "confident expression"

Action Verbs: standing, sitting, walking, running, lying, kneeling,
             posing, leaning, reclining, crouching

Gaze: looking at camera, looking directly, looking away,
     looking down, looking up, looking to the side

Expression: neutral, confident, serious, playful, sensual,
           relaxed, pensive, happy, smiling
[Environment/Scene] Patterns
├── "against [a/the]? [background description]"
│   Examples: "against a plain grey background"
│
├── "in [a/the]? [location]"
│   Examples: "in a studio", "in an outdoor setting"
│
├── "[location type] background/setting/scene"
│   Examples: "studio background", "outdoor scene"
│
├── "background is [description]"
│   Examples: "background is plain and light"

Location Terms: indoor, outdoor, studio, bedroom, street,
               beach, forest, city, garden, office

Background Terms: plain, simple, gradient, white, grey, black,
                 blurred, bokeh, natural
[Lighting] Patterns
├── "[quality] light/lighting"
│   Examples: "soft lighting", "natural light"
│
├── "lighting is [description]"
│   Examples: "lighting is soft and even"
│
├── "[tone] tones"
│   Examples: "warm tones", "cool tones"

Quality: soft, hard, natural, artificial, dramatic, dim, bright,
        diffused, harsh, even, flat

Tone: warm, cool, neutral, muted, vibrant, desaturated

Style: high key, low key, rim light, backlit, side lit,
      front lit, Rembrandt
[Style/Medium] Patterns
├── "[style] photograph/photo/shot/shoot"
│   Examples: "photo-realistic shoot", "fashion photograph"
│
├── "[medium] style"
│   Examples: "minimalist style", "editorial style"

Medium: photograph, photo, portrait, editorial, fashion,
       artistic, cinematic, fine art, commercial

Style: minimalist, modern, vintage, retro, classic,
      professional, high-end, casual
[Composition] Patterns
├── "[angle] camera angle/view/shot"
│   Examples: "front camera angle", "low angle shot"
│
├── "[framing] shot"
│   Examples: "cowboy shot", "full body shot", "close-up"

Angle: front, side, back, low, high, dutch, bird's eye,
      worm's eye, three-quarter

Framing: close-up, medium shot, full body, cowboy shot,
        portrait, wide shot, headshot, bust shot

Rules: centered, rule of thirds, symmetry, diagonal,
      golden ratio

META Patterns (Filter Out)

These patterns indicate meta-commentary that should be removed, not categorized:

FILTER PATTERNS:
├── "the image/photo/picture [shows/captures/depicts/features/has]"
├── "in the [middle/center/foreground/background] of the [image/frame]"
├── "the overall [mood/tone/atmosphere/aesthetic/look]"
├── "with a focus on"
├── "showcasing [her/his/the]"
├── "which [adds/creates/gives/lends]"
├── "conveying a sense of"
├── "creating [a/an] [sense/feeling/atmosphere] of"
├── "the viewer [can/is/may]"
├── "it is worth noting"

Action: Remove entire clause if META pattern matches anywhere in clause.


Multi-Match Handling

A single clause can match multiple categories:

INPUT CLAUSE:
"she has long brown hair and brown eyes"

EXTRACTION:
├── Match 1: "[subject] has [hair description]"
│   → [Subject Details/hair]: "long brown hair"
│
└── Match 2: "[color] eyes"
    → [Subject Details/eyes]: "brown eyes"

RESULT: Both extractions kept, clause fully consumed

Unmatched Content Handling

CLAUSE: "with the Calvin Klein logo on the front"

Pattern Match: None directly

FALLBACK STRATEGY:
1. Check if clause modifies previous extraction
   → Previous: "white crop top" [Subject Details/clothing]
   → This clause is a modifier → append to clothing
   → Result: "white crop top with the Calvin Klein logo"

2. If no modifier relationship, use keyword classification
   → "logo" suggests clothing/branding
   → Assign to [Subject Details/clothing]

3. If still unmatched, preserve in [Uncategorized]
   → Will be processed in Assembly phase

Full Example Walkthrough

INPUT:

"A photo-realistic shoot from a front camera angle about a model in a
white Calvin Klein crop top and matching briefs, photographed against
a plain light grey background. a woman standing in the middle of the
image, with a neutral expression, looking directly at the camera. she
has long, wavy brown hair and is wearing a white crop top with the
Calvin Klein logo on the front, paired with matching white briefs."

SENTENCE 1:

"A photo-realistic shoot from a front camera angle about a model in a
white Calvin Klein crop top and matching briefs, photographed against
a plain light grey background."

Clauses:
├── "A photo-realistic shoot from a front camera angle about a model"
│   → [Style/Medium]: "photo-realistic shoot"
│   → [Composition]: "front camera angle"
│   → [Subject]: "model"
│
├── "in a white Calvin Klein crop top"
│   → [Subject Details/clothing]: "white Calvin Klein crop top"
│
├── "and matching briefs"
│   → [Subject Details/clothing]: "matching briefs"
│
└── "photographed against a plain light grey background"
    → [Environment]: "plain light grey background"

SENTENCE 2:

"a woman standing in the middle of the image, with a neutral expression,
looking directly at the camera."

Clauses:
├── "a woman standing in the middle of the image"
│   → [Subject]: "a woman"
│   → [Action/Pose]: "standing"
│   → [META - FILTER]: "in the middle of the image" ← REMOVED
│
├── "with a neutral expression"
│   → [Action/Pose]: "neutral expression"
│
└── "looking directly at the camera"
    → [Action/Pose]: "looking directly at the camera"

SENTENCE 3:

"she has long, wavy brown hair and is wearing a white crop top with
the Calvin Klein logo on the front, paired with matching white briefs."

Clauses:
├── "she has long, wavy brown hair"
│   → [Subject Details/hair]: "long, wavy brown hair"
│
├── "and is wearing a white crop top with the Calvin Klein logo on the front"
│   → [Subject Details/clothing]: "white crop top with Calvin Klein logo"
│
└── "paired with matching white briefs"
    → [Subject Details/clothing]: "matching white briefs"

AGGREGATED OUTPUT:

{
  "Subject": ["model", "a woman"],

  "Subject Details": {
    "hair": ["long, wavy brown hair"],
    "clothing": [
      "white Calvin Klein crop top",
      "matching briefs",
      "white crop top with Calvin Klein logo",
      "matching white briefs"
    ]
  },

  "Action/Pose": [
    "standing",
    "neutral expression",
    "looking directly at the camera"
  ],

  "Environment": ["plain light grey background"],

  "Style/Medium": ["photo-realistic shoot"],

  "Composition": ["front camera angle"],

  "Filtered (META)": ["in the middle of the image"]
}

Phase 4: Assembly

4.1 Grouping & Ranking

For each category:
    tokens = filter(all_tokens, category == this_category)

    # Remove duplicates within category
    tokens = deduplicate(tokens)

    # Rank by confidence
    tokens = sort(tokens, key=confidence, descending=True)

    result[category] = tokens

4.2 Output Structure

ClassifiedImage {
    source_file: string
    extraction_timestamp: datetime
    total_tokens: int

    categories: {
        "Quality Boosters": [...],
        "Subject": [...],
        "Subject Details": {
            "hair": [...],
            "eyes": [...],
            "body": [...],
            "clothing": [...],
            ...
        },
        "Action/Pose": [...],
        "Environment/Scene": [...],
        "Lighting": [...],
        "Style/Medium": [...],
        "Composition": [...],
        "Technical Parameters": [...]
    },

    uncategorized: [...],
    filtered_meta: [...]
}

Performance Optimization

Caching Strategy

Token → Classification Cache (LRU, 10k entries)
    │
    ├── Key: normalized_token_text
    ├── Value: (category, confidence, timestamp)
    │
    └── Benefits:
        - Same tag across images → instant lookup
        - Batch processing efficiency
        - Reduces embedding computation

Batch Processing

For multiple images:
    → Collect unique tokens across batch
    → Classify unique tokens (batch embedding)
    → Distribute classifications back to images

Estimated Performance

Component Time per Token Batch Size Impact
Deterministic Rules <0.01ms None
Dictionary Lookup <0.1ms None
Fuzzy Match 1-5ms Minor
Embedding + Similarity 5-20ms 10x speedup with batching
Full Pipeline (cached) <1ms N/A
Full Pipeline (cold) 10-50ms 5-10x speedup

Accuracy Improvement Strategies

1. Active Learning Loop

Unclassified/Low-Confidence Tokens
    → Human Review Interface
    → Manual Classification
    → Add to Training Set
    → Retrain Classifier Weekly
    → Update Dictionaries

2. Confidence Calibration

  • Track prediction accuracy per confidence bucket
  • Adjust thresholds based on real-world performance
  • Different thresholds per category (some are easier)

3. Category-Specific Models

For high-importance categories, train specialized classifiers:

  • Subject Details → clothing classifier
  • Lighting → tone/lighting classifier
  • These can be more accurate than generic approach

4. Ensemble Voting

classification_1 = dictionary_lookup(token)
classification_2 = embedding_similarity(token)
classification_3 = trained_classifier(token)

final = weighted_vote([
    (classification_1, 0.3),
    (classification_2, 0.35),
    (classification_3, 0.35)
])

Implementation Priority

Phase Priority Accuracy Effort
1. Tokenization Pipeline High N/A Medium
2. Deterministic Rules (Layer 1-2) High 95%+ Low
3. Keyword Dictionaries (Layer 3) High 85%+ Medium
4. Embedding Similarity (Layer 4) Medium 75%+ Medium
5. Trained Classifier (Layer 5) Low 80%+ High
6. Caching & Optimization Medium N/A Medium
7. Active Learning Loop Low +5-10% High

Validation & Quality Analysis

Overview

This section defines the approach for validating whether the tokenization and classification pipeline is working correctly.

Goals:

  1. Completeness: Did we lose any content?
  2. Correctness: Is each phrase in the right category?
  3. Cleanliness: Did we properly filter meta-commentary?

Validation Strategy 1: Content Preservation Check

Concept

Every meaningful word/phrase from the input caption should appear in exactly one output category (or be explicitly filtered as META).

┌─────────────────────────────────────────────────────────────────┐
│                    CONTENT ACCOUNTING                            │
│                                                                  │
│  INPUT CAPTION                                                   │
│  ─────────────────                                               │
│  Word count: 47                                                  │
│  Unique content words: 32 (excluding: a, the, is, and, with)    │
│                                                                  │
│  OUTPUT CATEGORIES                                               │
│  ─────────────────────                                           │
│  Words in [Subject]: 3                                           │
│  Words in [Subject Details]: 12                                  │
│  Words in [Action/Pose]: 6                                       │
│  Words in [Environment]: 4                                       │
│  Words in [Style]: 2                                             │
│  Words in [Composition]: 3                                       │
│  Words in [META/Filtered]: 5                                     │
│                                                                  │
│  TOTAL ACCOUNTED: 35                                             │
│  UNACCOUNTED: 0                                                  │
│                                                                  │
│  COVERAGE: 100% ✓                                                │
└─────────────────────────────────────────────────────────────────┘

Metrics

Metric Formula Target
Coverage (words in categories + filtered) / total content words > 95%
Loss Rate unaccounted words / total content words < 5%
Filter Rate filtered words / total content words 5-15% typical

Output: Unaccounted Words Report

UNACCOUNTED WORDS:
├── "Calvin" - appeared in input, not in any output
├── "Klein" - appeared in input, not in any output
└── "logo" - appeared in input, not in any output

DIAGNOSIS: Clothing pattern missed brand names
ACTION: Expand clothing pattern to capture brand + logo terms

Validation Strategy 2: Cross-Reference with Tags

Concept

Tags from wd14/joytag/pixai are independently generated labels. If extraction is correct, extracted phrases should align with these tags.

┌─────────────────────────────────────────────────────────────────┐
│                 TAG ↔ EXTRACTION ALIGNMENT                       │
│                                                                  │
│  TAG SOURCE: wd14                                                │
│  ──────────────                                                  │
│  "brown hair" (0.915) ←→ [Subject Details/hair]: "brown hair" ✓ │
│  "long hair" (0.9)    ←→ [Subject Details/hair]: "long...hair" ✓│
│  "standing" (0.581)   ←→ [Action/Pose]: "standing" ✓            │
│  "brown eyes" (0.87)  ←→ [Subject Details/eyes]: ??? ✗          │
│  "sports bra" (0.868) ←→ [Subject Details/clothing]: "crop top" ~│
│                                                                  │
│  ALIGNMENT SCORE: 4/5 = 80%                                      │
│  SEMANTIC MATCH: 4.5/5 = 90% (sports bra ≈ crop top)            │
└─────────────────────────────────────────────────────────────────┘

Alignment Types

Type Definition Example
Exact Match Tag text appears in extraction "brown hair" ↔ "brown hair"
Semantic Match Different words, same meaning "sports bra" ↔ "crop top"
Partial Match Tag is subset of extraction "hair" ↔ "long wavy brown hair"
Category Match Tag and extraction in same category Both in [Subject Details/hair]
Missing Tag has no corresponding extraction "brown eyes" not extracted
Conflict Tag and extraction in different categories Tag says pose, extraction says clothing

Metrics

Metric Formula Target
Tag Coverage tags with matching extraction / total tags > 80%
Category Accuracy correct category matches / total matches > 90%
Conflict Rate category conflicts / total matches < 5%

Validation Strategy 3: Category Coherence Analysis

Concept

Content within a category should be semantically related. Content across categories should be semantically distinct.

┌─────────────────────────────────────────────────────────────────┐
│              INTRA-CATEGORY COHERENCE                            │
│                                                                  │
│  [Subject Details/hair]:                                         │
│  ├── "long wavy brown hair"                                      │
│  ├── "dark hair"                                                 │
│  └── "hair styled in loose waves"                                │
│                                                                  │
│  Pairwise Similarity (embedding cosine):                         │
│  ├── (1,2): 0.82                                                 │
│  ├── (1,3): 0.78                                                 │
│  └── (2,3): 0.71                                                 │
│                                                                  │
│  MEAN INTRA-SIMILARITY: 0.77 ✓ (good coherence)                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│              INTER-CATEGORY SEPARATION                           │
│                                                                  │
│  [Subject Details/hair] vs [Environment]:                        │
│  ├── "long wavy brown hair" ↔ "grey background": 0.12           │
│  ├── "dark hair" ↔ "studio setting": 0.08                       │
│                                                                  │
│  MEAN INTER-SIMILARITY: 0.10 ✓ (good separation)                │
└─────────────────────────────────────────────────────────────────┘

Coherence Score

Category Coherence = (Mean Intra-Similarity) / (Mean Inter-Similarity)

Good: > 3.0 (within-category 3x more similar than across)
Acceptable: 2.0 - 3.0
Poor: < 2.0 (categories not well separated)

Contamination Detection

CONTAMINATION CHECK:
─────────────────────
[Action/Pose] contains:
├── "standing confidently" ✓ (belongs here)
├── "neutral expression" ✓ (belongs here)
└── "white crop top" ✗ (should be in clothing!)

CONTAMINATION: 1 item misplaced
CONTAMINATION RATE: 1/3 = 33% ← Problem!

Validation Strategy 4: Round-Trip Reconstruction

Concept

If we extract correctly and reassemble, the meaning should be preserved.

┌─────────────────────────────────────────────────────────────────┐
│                    ROUND-TRIP TEST                               │
│                                                                  │
│  ORIGINAL CAPTION:                                               │
│  "A young woman with long brown hair, wearing a white dress,    │
│   standing in a garden with soft natural lighting."              │
│                                                                  │
│  EXTRACTED → REASSEMBLED:                                        │
│  "A young woman. She has long brown hair. She is wearing a      │
│   white dress. She is standing. The scene is set in a garden.   │
│   The lighting features soft natural lighting."                  │
│                                                                  │
│  SEMANTIC SIMILARITY (embedding):                                │
│  Original ↔ Reconstructed: 0.89                                  │
│                                                                  │
│  VERDICT: High preservation ✓                                    │
└─────────────────────────────────────────────────────────────────┘

What Round-Trip Catches

Issue Symptom
Lost content Similarity drops significantly
Wrong categorization Reconstructed sentence sounds wrong
Over-filtering Important details missing in reconstruction
Duplication Same phrase appears multiple times

Validation Strategy 5: META Filter Accuracy

Concept

Verify that filtered content is truly meta-commentary, not descriptive content.

┌─────────────────────────────────────────────────────────────────┐
│                META FILTER VALIDATION                            │
│                                                                  │
│  FILTERED PHRASES:                                               │
│  ├── "in the middle of the image" ← Correct (positional meta)   │
│  ├── "the image captures" ← Correct (meta preamble)             │
│  ├── "showcasing her curves" ← Correct (meta commentary)        │
│  └── "beautiful woman" ← INCORRECT! (descriptive content)       │
│                                                                  │
│  FALSE POSITIVE: "beautiful woman" was filtered but is content  │
│  ACTION: Refine filter to not catch adjective+noun phrases      │
└─────────────────────────────────────────────────────────────────┘

Filter Metrics

Metric Definition Target
True Positive Rate Correctly filtered meta / total meta > 95%
False Positive Rate Incorrectly filtered content / total content < 2%
False Negative Rate Missed meta / total meta < 10%

Manual Review Sample

For validation, manually label 50-100 filtered phrases:

  • ✓ Correctly filtered (true meta)
  • ✗ Incorrectly filtered (was real content)
  • ? Ambiguous

Validation Strategy 6: Ground Truth Benchmark

Concept

Create a manually annotated test set of 20-50 captions with correct categorizations.

┌─────────────────────────────────────────────────────────────────┐
│                 GROUND TRUTH COMPARISON                          │
│                                                                  │
│  TEST CAPTION #7:                                                │
│  "A woman in a red dress standing by the ocean at sunset"       │
│                                                                  │
│  GROUND TRUTH (manual):                 SYSTEM OUTPUT:           │
│  ────────────────────                   ─────────────────        │
│  [Subject]: "woman"                     [Subject]: "woman" ✓     │
│  [Clothing]: "red dress"                [Clothing]: "red dress" ✓│
│  [Action]: "standing"                   [Action]: "standing" ✓   │
│  [Environment]: "by the ocean"          [Environment]: "ocean" ~  │
│  [Lighting]: "at sunset"                [Lighting]: "sunset" ✓   │
│                                                                  │
│  PRECISION: 5/5 = 100%                                           │
│  RECALL: 4.5/5 = 90% (partial on environment)                   │
│  F1 SCORE: 0.95                                                  │
└─────────────────────────────────────────────────────────────────┘

Benchmark Metrics

Metric Per Category Overall
Precision TP / (TP + FP) Macro average
Recall TP / (TP + FN) Macro average
F1 Score 2 * P * R / (P + R) Macro average

Combined Validation Report

╔═══════════════════════════════════════════════════════════════════╗
║            EXTRACTION QUALITY REPORT                               ║
║            Caption: florence_description                           ║
║            Date: 2024-12-27                                        ║
╠═══════════════════════════════════════════════════════════════════╣
║                                                                    ║
║  1. CONTENT PRESERVATION                                           ║
║     Coverage: 96.2%  ✓                                             ║
║     Lost words: ["Calvin", "Klein"] (brand names)                  ║
║                                                                    ║
║  2. TAG ALIGNMENT                                                  ║
║     Matched: 18/22 tags (81.8%)  ✓                                 ║
║     Category accuracy: 17/18 (94.4%)  ✓                            ║
║     Missing: ["brown eyes", "cowboy shot", "3d", "thigh gap"]     ║
║                                                                    ║
║  3. CATEGORY COHERENCE                                             ║
║     [Subject Details/hair]: 0.81 intra  ✓                          ║
║     [Subject Details/clothing]: 0.73 intra  ✓                      ║
║     [Action/Pose]: 0.68 intra  ~                                   ║
║     [Environment]: 0.85 intra  ✓                                   ║
║     Overall separation ratio: 4.2  ✓                               ║
║                                                                    ║
║  4. META FILTERING                                                 ║
║     Filtered: 3 phrases                                            ║
║     False positives: 0  ✓                                          ║
║     Missed meta: 1 ("with a focus on her curves")                  ║
║                                                                    ║
║  5. ROUND-TRIP SIMILARITY                                          ║
║     Original ↔ Reconstructed: 0.87  ✓                              ║
║                                                                    ║
╠═══════════════════════════════════════════════════════════════════╣
║  OVERALL SCORE: 91/100  GRADE: A-                                  ║
║                                                                    ║
║  ISSUES TO ADDRESS:                                                ║
║  1. Brand name extraction (Calvin Klein)                           ║
║  2. Missing eye color extraction                                   ║
║  3. One meta phrase not filtered                                   ║
╚═══════════════════════════════════════════════════════════════════╝

Validation Module Structure

ValidationAnalyzer
├── ContentPreservationChecker
│   ├── tokenize_input()
│   ├── collect_output_tokens()
│   ├── compute_coverage()
│   └── report_unaccounted()
│
├── TagAlignmentChecker
│   ├── load_tags_from_metadata()
│   ├── match_tags_to_extractions()
│   ├── classify_match_type()
│   └── compute_alignment_score()
│
├── CoherenceAnalyzer
│   ├── embed_category_contents()
│   ├── compute_intra_similarity()
│   ├── compute_inter_similarity()
│   └── detect_contamination()
│
├── MetaFilterValidator
│   ├── collect_filtered_phrases()
│   ├── check_against_patterns()
│   └── flag_potential_false_positives()
│
├── RoundTripTester
│   ├── reconstruct_from_categories()
│   ├── compute_semantic_similarity()
│   └── identify_lost_content()
│
└── ReportGenerator
    ├── aggregate_metrics()
    ├── compute_overall_score()
    └── generate_actionable_items()

Running Validation

INPUT:
├── Original metadata (with all captions and tags)
├── Extraction output (categorized tokens)

PROCESS:
├── Run all validation strategies
├── Aggregate results
├── Generate report

OUTPUT:
├── Quality score (0-100)
├── Per-category breakdown
├── Specific issues to fix
├── Unaccounted content list

Appendix: Canonical Categories Reference

Category Subcategories Description
Quality Boosters - Technical quality terms
Subject - Who/what is the main subject
Subject Details hair, eyes, face, body, clothing, skin Appearance details
Action/Pose - What they're doing, expression, gaze
Environment/Scene - Setting, location, background
Lighting - Light quality, direction, mood, tones
Style/Medium - Artistic style, medium
Composition - Framing, camera angle, shot type
Technical Parameters - Resolution, aspect ratio, artifacts

Phase 5: Assembly (Rule-Based Text Generation)

Overview

This phase converts classified tokens into natural, readable text without using LLMs. The approach uses:

  • Token → Natural Phrase Mappings
  • Category-specific templates
  • Grammar rules (adjective ordering, pronoun resolution)
  • Sentence fusion techniques

Assembly Pipeline

┌─────────────────────────────────────────────────────────────────┐
│                  Classified Tokens (per category)                │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              STEP 1: Token → Phrase Mapping                      │
│                                                                  │
│  "1girl" → "a woman"                                            │
│  "looking at viewer" → "looking directly at the camera"         │
│  "cowboy shot" → "framed from mid-thigh up"                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              STEP 2: Attribute Extraction & Ordering             │
│                                                                  │
│  Hair: [long, wavy, brown] → ordered by [length, texture, color]│
│  Result: "long, wavy brown hair"                                │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              STEP 3: Template Application                        │
│                                                                  │
│  Template: "with {length}, {texture} {color} hair"              │
│  Result: "with long, wavy brown hair"                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              STEP 4: Sentence Fusion                             │
│                                                                  │
│  Input: ["She has long hair", "brown hair", "wavy hair"]        │
│  Output: "She has long, wavy brown hair"                        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│              STEP 5: Paragraph Assembly                          │
│                                                                  │
│  Combine sections with appropriate connectors                   │
│  Apply pronoun resolution                                       │
│  Handle empty categories                                        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Final Readable Text                          │
└─────────────────────────────────────────────────────────────────┘

Category-Specific Generation Strategies

1. [Subject] Generation

Input Tokens:

["1girl", "solo", "young woman", "mid-twenties"]

Semantic Mapping:

Token Pattern Role Example
1girl, 1boy, 1woman count + gender "a woman"
solo composition marker (implies singular)
young, mid-twenties age modifier "young", "in her mid-twenties"
2girls, multiple plurality "two women"

Token → Natural Phrase Mapping:

{
    "1girl": "a woman",
    "1boy": "a man",
    "2girls": "two women",
    "solo": null,  # absorbed into singularity
    "young woman": "a young woman",
    "mid-twenties": "in her mid-twenties",
    "model": "a model",
    "asian": "Asian"
}

Template:

SUBJECT_TEMPLATE = "{article} {age_modifier} {ethnicity} {gender_noun} {age_phrase}"

Where:
- article = "a" | "an" | "" (for plurals)
- age_modifier = "young" | ""
- ethnicity = "Asian" | "" (optional, only if present)
- gender_noun = "woman" | "man" | "women" | "model"
- age_phrase = "in her mid-twenties" | ""

Generation Logic:

def generate_subject(tokens):
    # Priority: most specific description wins
    if "young woman" in tokens:
        base = "a young woman"
    elif "1girl" in tokens or "1woman" in tokens:
        base = "a woman"
    elif "model" in tokens:
        base = "a model"
    else:
        base = "a person"

    # Add age phrase if different from base
    if "mid-twenties" in tokens and "young" not in base:
        base += " in her mid-twenties"

    # Add ethnicity if present
    if "asian" in tokens:
        base = base.replace("a ", "an Asian ")

    return base

Output:

"A young woman in her mid-twenties"

2. [Subject Details] Generation

This is the most complex category with multiple subcategories.

Input Tokens (organized):

{
    "hair": ["long hair", "wavy hair", "brown hair"],
    "eyes": ["brown eyes", "looking at viewer"],
    "face": ["lips", "parted lips", "neutral expression", "fair complexion"],
    "body": ["slim", "curvy", "navel", "breasts", "medium breasts"],
    "clothing": ["white crop top", "white panties", "Calvin Klein", "underwear"]
}

A. Hair Description

Ordering Rules:

HAIR_ORDER = [length, texture, color, style]

length: short, medium, long
texture: straight, wavy, curly
color: brown, blonde, black, red, etc.
style: ponytail, bun, braids, etc.

Template:

"with {length} {texture} {color} hair"
OR
"with {color} hair styled in {style}"

Token Consolidation:

Input: ["long hair", "wavy hair", "brown hair"]

Step 1: Extract attributes
    length = "long"
    texture = "wavy"
    color = "brown"

Step 2: Apply template
    "with long, wavy brown hair"

Grammar Rules:

  • If multiple adjectives: use comma between different types
  • Color always comes last before noun
  • "long, wavy brown hair" ✓
  • "brown long wavy hair" ✗

B. Eyes Description

Template:

"{color} eyes {gaze_phrase}"

Gaze Phrase Mapping:

{
    "looking at viewer": "looking directly at the camera",
    "looking away": "looking away",
    "closed eyes": "with eyes closed",
    "looking down": "gazing downward"
}

Output:

"brown eyes looking directly at the camera"

C. Face/Expression Description

Template:

"with a {expression} expression and {complexion} complexion"
OR
"with {lip_description}"

Generation:

Input: ["neutral expression", "fair complexion", "parted lips"]

Output: "with a neutral expression, fair complexion, and slightly parted lips"

D. Body Description

Filtering: Some tokens are implicit/redundant for natural description

Include vs Exclude:

Include (natural): slim, curvy, toned, athletic, petite
Exclude (tag-like): navel, breasts, thighs, stomach (unless specifically relevant)

Template:

"with a {body_type} figure"
OR
"with a {body_type} build"

Output:

"with a slim, curvy figure"

E. Clothing Description

Ordering Rules:

CLOTHING_ORDER = [upper_body, lower_body, footwear, accessories]

Grouping Logic:

Input: ["white crop top", "white panties", "Calvin Klein logo"]

Step 1: Identify garments
    upper = "white crop top"
    lower = "white panties"
    brand = "Calvin Klein"

Step 2: Combine logically
    If brand present and matches items:
        "a white Calvin Klein crop top and matching panties"
    Else:
        "a white crop top paired with white panties"

Connector Words:

{
    "matching_set": "and matching",
    "contrast": "paired with",
    "layered": "over",
    "accessory": "with"
}

Output:

"wearing a white Calvin Klein crop top with matching white briefs"

F. Combined Subject Details Generation

Master Template:

SUBJECT_DETAILS_TEMPLATE = """
{hair_description}, {eye_description}.
She has {face_description} and {body_description}.
She is {clothing_description}.
"""

Full Output:

"with long, wavy brown hair and brown eyes looking directly at the camera.
She has a neutral expression with fair complexion and slightly parted lips,
and a slim, curvy figure. She is wearing a white Calvin Klein crop top
with matching white briefs."

3. [Action/Pose] Generation

Input Tokens:

["standing", "arms at sides", "facing viewer", "cowboy shot"]

Token → Phrase Mapping:

{
    "standing": "standing",
    "sitting": "sitting",
    "arms at sides": "with her arms at her sides",
    "hands on hips": "with her hands on her hips",
    "facing viewer": "facing the camera",
    "cowboy shot": null,  # composition, not action
    "looking at viewer": "looking at the viewer"
}

Template:

ACTION_TEMPLATE = "{primary_pose} {arm_position}, {facing_direction}"

Ordering:

1. Primary pose (standing, sitting, lying)
2. Body position (arms, legs)
3. Facing direction
4. Gaze direction (if different from facing)

Output:

"standing confidently with her arms at her sides, facing the camera"

4. [Environment/Scene] Generation

Input Tokens:

["indoor", "plain light grey background", "studio", "simple background"]

Deduplication & Priority:

Priority: specific > general
"plain light grey background" > "simple background"
"studio" + "indoor" → "indoor studio setting"

Templates:

BACKGROUND_TEMPLATES = [
    "against a {background_description}",
    "in {location_type} with a {background_description}",
    "set against a {background_description}"
]

Output:

"against a plain, light grey background in an indoor studio setting"

5. [Lighting] Generation

Input Tokens:

["soft light", "even lighting", "high key", "neutral tones", "low contrast"]

Semantic Grouping:

light_quality: soft, hard, natural, artificial
light_style: high key, low key, dramatic
tones: neutral, warm, cool, muted
contrast: low, high

Template:

LIGHTING_TEMPLATE = "The lighting is {quality} and {style}, with {tones} and {contrast} contrast."

OR (simpler)

"lit with {quality} {style} lighting"

Natural Combinations:

{
    ("soft light", "even lighting"): "soft, even lighting",
    ("high key", "bright"): "bright, high-key lighting",
    ("natural light", "warm tones"): "warm natural lighting"
}

Output:

"The image features soft, even lighting with neutral tones and low contrast."

6. [Style/Medium] Generation

Input Tokens:

["photo realistic", "photograph", "minimalist aesthetic", "modern", "professional"]

Template:

STYLE_TEMPLATE = "A {style} {medium} with a {aesthetic} aesthetic."

OR

"This is a {style} {medium}, {aesthetic_description}."

Token Categories:

medium: photograph, digital art, illustration, 3D render
style: realistic, stylized, artistic
aesthetic: minimalist, modern, vintage, professional

Output:

"A photo-realistic photograph with a minimalist, modern aesthetic."

7. [Composition] Generation

Input Tokens:

["front view", "full body", "centered", "cowboy shot", "portrait orientation", "rule of thirds"]

Template:

COMPOSITION_TEMPLATE = "Shot from a {camera_angle}, showing {framing}, {composition_style}."

Mappings:

camera_angle: front view, side view, three-quarter view, from above, from below
framing: full body, upper body, close-up, medium shot, cowboy shot
composition_style: centered, rule of thirds, symmetrical
orientation: portrait, landscape

Natural Phrasing:

{
    "cowboy shot": "framed from mid-thigh up",
    "full body": "showing the full figure",
    "front view": "from a frontal angle",
    "centered": "with the subject centered",
    "rule of thirds": "following the rule of thirds"
}

Output:

"Captured from a frontal angle, framed from mid-thigh up, with the subject centered in the frame."

8. [Quality Boosters] Generation

Strategy: These are typically not verbalized in natural descriptions, but can be used as prefix qualifiers.

Input Tokens:

["realistic", "sharp", "detailed", "professional quality", "high resolution"]

Template (if needed):

QUALITY_TEMPLATE = "A {quality_list} image..."

Output:

"A sharp, highly detailed, professional-quality image..."

Alternative: Integrate into Style/Medium

"A sharp, realistic photograph with professional quality..."

9. [Technical Parameters] Generation

Strategy: Usually omitted from natural descriptions, but can be appended as metadata.

Input Tokens:

["288x512", "portrait orientation", "2:3 aspect ratio"]

Template (if needed):

TECHNICAL_TEMPLATE = "Image dimensions: {width}x{height} ({orientation}, {aspect_ratio})"

Output (usually separate or omitted):

"(288×512 pixels, portrait orientation, 2:3 aspect ratio)"

Complete Assembly: Full Description Generation

Master Template Structure

FULL_DESCRIPTION_TEMPLATE = """
{quality_prefix}{style_medium}

{subject_intro} {subject_details_hair}, {subject_details_eyes}. {subject_details_face}. {subject_details_body}. {clothing_description}.

{action_pose}.

{environment_scene}. {lighting_description}.

{composition_description}.
"""

Assembly Algorithm

def generate_full_description(classified_tokens):
    sections = {}

    # Generate each section independently
    sections['quality'] = generate_quality(classified_tokens['Quality Boosters'])
    sections['style'] = generate_style(classified_tokens['Style/Medium'])
    sections['subject'] = generate_subject(classified_tokens['Subject'])
    sections['details'] = generate_subject_details(classified_tokens['Subject Details'])
    sections['action'] = generate_action(classified_tokens['Action/Pose'])
    sections['environment'] = generate_environment(classified_tokens['Environment/Scene'])
    sections['lighting'] = generate_lighting(classified_tokens['Lighting'])
    sections['composition'] = generate_composition(classified_tokens['Composition'])

    # Assemble with appropriate connectors
    paragraphs = []

    # Paragraph 1: Style intro
    p1 = f"{sections['quality']} {sections['style']}"
    paragraphs.append(p1.strip())

    # Paragraph 2: Subject description
    p2 = f"{sections['subject']} {sections['details']}"
    paragraphs.append(p2.strip())

    # Paragraph 3: Action and environment
    p3 = f"{sections['action']}. {sections['environment']}"
    paragraphs.append(p3.strip())

    # Paragraph 4: Technical (lighting, composition)
    p4 = f"{sections['lighting']} {sections['composition']}"
    paragraphs.append(p4.strip())

    # Join paragraphs
    return "\n\n".join(paragraphs)

Example: Full Pipeline Execution

Input (Classified Tokens)

{
    "Quality Boosters": ["realistic", "sharp", "professional quality"],
    "Subject": ["1girl", "solo", "young woman", "mid-twenties"],
    "Subject Details": {
        "hair": ["long hair", "wavy hair", "brown hair"],
        "eyes": ["brown eyes", "looking at viewer"],
        "face": ["neutral expression", "fair complexion", "parted lips"],
        "body": ["slim", "curvy"],
        "clothing": ["white crop top", "white panties", "Calvin Klein"]
    },
    "Action/Pose": ["standing", "arms at sides", "facing viewer"],
    "Environment/Scene": ["indoor", "plain light grey background", "studio"],
    "Lighting": ["soft light", "even lighting", "neutral tones", "low contrast"],
    "Style/Medium": ["photograph", "photo realistic", "minimalist", "modern"],
    "Composition": ["front view", "cowboy shot", "centered", "portrait orientation"]
}

Output (Generated Description)

A sharp, professional-quality photograph with a minimalist, modern aesthetic.

A young woman in her mid-twenties with long, wavy brown hair and brown eyes
looking directly at the camera. She has a neutral expression with fair complexion
and slightly parted lips, and a slim, curvy figure. She is wearing a white
Calvin Klein crop top with matching white briefs.

She stands confidently with her arms at her sides, facing the camera. The scene
is set against a plain, light grey background in an indoor studio setting.

The image features soft, even lighting with neutral tones and low contrast.
Captured from a frontal angle, framed from mid-thigh up, with the subject
centered in the frame.

Advanced Techniques for Better Output

1. Sentence Variation

Problem: Same template = repetitive output

Solution: Multiple template variants per category

SUBJECT_TEMPLATES = [
    "{article} {age} {gender}",
    "{article} {gender} {age_phrase}",
    "{article} {ethnicity} {gender} who appears to be {age_phrase}",
]

# Random or round-robin selection
template = random.choice(SUBJECT_TEMPLATES)

2. Connector Variation

CONNECTORS = {
    "addition": ["and", "with", "featuring", "along with"],
    "contrast": ["but", "while", "yet"],
    "elaboration": ["specifically", "particularly", "notably"]
}

# Vary connector usage
connector = random.choice(CONNECTORS["addition"])

3. Sentence Fusion

Before:

"She has long hair. Her hair is brown. Her hair is wavy."

After (fused):

"She has long, wavy brown hair."

Fusion Rules:

  • Same subject + different attributes → combine attributes
  • Use comma for adjective lists
  • Apply adjective ordering rules

4. Pronoun Resolution

Track subject for pronoun usage:

First mention: "A young woman..."
Subsequent: "She has..." / "Her hair..." / "She is wearing..."

Gender-neutral option:

First mention: "A person..."
Subsequent: "They have..." / "Their hair..."

5. Confidence-Based Inclusion

def should_include(token, threshold=0.6):
    return token.confidence >= threshold

# Only include high-confidence details
details = [t for t in tokens if should_include(t)]

6. Avoiding Redundancy

Rule: Don't repeat information

# If "young woman" is used in Subject, don't repeat "young" in details
if "young" in subject_text:
    remove "young" from subject_details modifiers

Implementation Components

Required Libraries (No ML)

Library Purpose
inflect Pluralization, articles (a/an)
pattern Grammar, conjugation
nltk (rules only) Tokenization, POS tagging (non-ML)

Core Data Structures

# Token to natural phrase dictionary
PHRASE_MAPPINGS = {
    category: {
        token: natural_phrase
    }
}

# Grammar rules
ADJECTIVE_ORDER = ["opinion", "size", "age", "shape", "color", "origin", "material"]

# Templates per category
TEMPLATES = {
    category: [template1, template2, ...]
}

# Connectors
CONNECTORS = {
    type: [variants]
}

Edge Cases & Solutions

1. Empty Categories

if not environment_tokens:
    # Option A: Skip entirely
    environment_text = ""

    # Option B: Generic fallback
    environment_text = "against a neutral background"

    # Option C: Infer from other categories
    if "photograph" in style_tokens:
        environment_text = "in a studio setting"

2. Conflicting Information

Input: ["standing", "sitting", "lying down"]  # Can't be all three

MUTUALLY_EXCLUSIVE = {
    "pose": ["standing", "sitting", "lying", "kneeling"],
    "gaze": ["looking at viewer", "looking away", "eyes closed"]
}

def resolve_conflicts(tokens, category):
    if category in MUTUALLY_EXCLUSIVE:
        # Keep highest confidence
        return max(tokens, key=lambda t: t.confidence)

3. Clothing Relationship Detection

def detect_clothing_relationship(items):
    colors = extract_colors(items)
    if len(set(colors)) == 1:
        return "matching"
    elif is_formal_set(items):
        return "paired_with"
    else:
        return "combined_with"

4. Context Passing for Pronouns

def generate_action(tokens, context):
    pronoun = context.get('subject_pronoun', 'They')
    return f"{pronoun} {action_phrase}"

Quality Improvements Without LLM

Technique Improvement Effort
More templates +Variety Low
Better phrase mappings +Naturalness Medium
Adjective ordering rules +Grammar Low
Sentence fusion +Fluency Medium
Pronoun tracking +Coherence Low
Confidence thresholds +Accuracy Low
Synonym variation +Variety Medium
Paragraph structure +Readability Low

Limitations vs LLM Approach

Aspect Rule-Based LLM
Consistency High (deterministic) Variable
Speed Very fast Slower
Cost Zero inference cost API/GPU cost
Creativity Limited to templates High
Edge cases Needs manual handling Handles gracefully
Maintenance Template updates Prompt tuning
Grammar errors Possible if rules incomplete Rare

Verbosity Control

VERBOSITY = {
    "minimal": {
        "subject_details": ["hair", "clothing"],
        "max_adjectives": 2
    },
    "standard": {
        "subject_details": ["hair", "eyes", "clothing", "body"],
        "max_adjectives": 3
    },
    "detailed": {
        "subject_details": "all",
        "max_adjectives": "unlimited"
    }
}

Phase 6: LLM-Based Assembly (Alternative)

Overview

This phase provides an alternative to rule-based assembly using Large Language Models for natural language generation. Use this when higher quality, more natural output is needed and latency/cost are acceptable.

When to Use LLM vs Rule-Based

Use Case Rule-Based LLM
High volume, low variance ✓ Best Overkill
Need creative variety Limited ✓ Best
Complex token relationships Brittle ✓ Best
Edge cases / unusual combinations Fails ✓ Handles gracefully
Strict format control ✓ Best Needs constraints
Cost-sensitive ✓ Best Expensive at scale
Real-time / low latency ✓ Best Slower

LLM Assembly Approaches

Approach 1: Single Comprehensive Prompt

Input Preparation:

Transform classified tokens into clean structured format:

{
  "Quality Boosters": ["realistic", "sharp", "professional quality"],
  "Subject": ["1girl", "solo", "young woman", "mid-twenties"],
  "Subject Details": {
    "hair": ["long hair", "wavy hair", "brown hair"],
    "eyes": ["brown eyes", "looking at viewer"],
    "face": ["neutral expression", "fair complexion", "parted lips"],
    "body": ["slim", "curvy"],
    "clothing": ["white crop top", "white panties", "Calvin Klein"]
  },
  "Action/Pose": ["standing", "arms at sides", "facing viewer"],
  "Environment/Scene": ["indoor", "plain light grey background", "studio"],
  "Lighting": ["soft light", "even lighting", "neutral tones", "low contrast"],
  "Style/Medium": ["photograph", "photo realistic", "minimalist", "modern"],
  "Composition": ["front view", "cowboy shot", "centered", "portrait orientation"]
}

System Prompt:

You are an expert image description writer. Your task is to convert structured
image metadata into natural, fluent, professional image descriptions.

RULES:
1. Write in third person, present tense
2. Be concise but comprehensive - aim for 3-4 paragraphs
3. Flow naturally - don't just list attributes
4. Prioritize important visual elements
5. Use appropriate art/photography terminology
6. Don't invent details not present in the input
7. Ignore duplicate or redundant tokens
8. Convert tag-style tokens (like "1girl") into natural language ("a woman")

STRUCTURE:
- Paragraph 1: Overall style, medium, and quality
- Paragraph 2: Subject description (appearance, clothing)
- Paragraph 3: Pose, action, and environment
- Paragraph 4: Lighting and composition (if notable)

OUTPUT FORMAT:
Return only the description text, no headers or labels.

User Prompt:

Convert the following classified image tokens into a natural, readable description:

{json_structured_tokens}

Expected Output:

A sharp, professional-quality photograph with a minimalist, modern aesthetic,
captured in a photo-realistic style.

The image features a young woman, appearing to be in her mid-twenties, with long,
wavy brown hair and brown eyes that gaze directly at the camera. She has a fair
complexion and a neutral expression, her lips slightly parted. Her figure is slim
yet curvy, and she wears a white Calvin Klein crop top paired with matching white briefs.

She stands confidently with her arms relaxed at her sides, facing the viewer
directly. The setting is an indoor studio with a plain, light grey background
that provides clean contrast to her attire.

The lighting is soft and even, creating neutral tones with low contrast that
complement the minimalist aesthetic. The shot is composed from a frontal angle,
framed from mid-thigh up in a classic cowboy shot, with the subject centered
in the portrait-oriented frame.

Approach 2: Section-by-Section Generation

Generate each section independently with focused prompts:

Subject Details Prompt:

Convert these subject detail tokens into 2-3 natural sentences describing
a person's appearance:

Hair: ["long hair", "wavy hair", "brown hair"]
Eyes: ["brown eyes", "looking at viewer"]
Face: ["neutral expression", "fair complexion", "parted lips"]
Body: ["slim", "curvy"]
Clothing: ["white crop top", "white panties", "Calvin Klein"]

Use natural flow, don't list attributes mechanically. Use "she/her" pronouns.

Advantages:

Benefit Explanation
Parallel execution 5 calls simultaneously = faster total time
Granular control Different prompts per section
Easier debugging Isolate which section has issues
Caching Cache common section outputs
Fallback If one fails, others still work

Approach 3: Few-Shot Prompting

Provide examples for consistent output style:

You convert structured image tokens into natural descriptions.

EXAMPLE 1:
Input:
{
  "Subject": ["1boy", "solo"],
  "Subject Details": {"hair": ["short black hair"], "clothing": ["blue suit", "red tie"]},
  "Action/Pose": ["sitting", "arms crossed"],
  "Environment/Scene": ["office", "window background"]
}

Output:
A man with short black hair sits with his arms crossed, dressed in a sharp blue
suit with a red tie. The setting is a professional office environment, with
windows visible in the background.

EXAMPLE 2:
Input:
{
  "Subject": ["2girls"],
  "Subject Details": {"hair": ["blonde hair", "black hair"], "clothing": ["summer dresses"]},
  "Action/Pose": ["walking", "holding hands"],
  "Environment/Scene": ["beach", "sunset"]
}

Output:
Two women walk hand in hand along the beach - one with flowing blonde hair,
the other with dark black hair. Both wear light summer dresses that catch the
warm glow of the sunset behind them.

NOW CONVERT:
Input:
{your_actual_tokens}

Output:

Approach 4: Structured Output with Schema

Force consistent structure using JSON mode:

Prompt:

Convert the image tokens into a structured description. Return JSON only.

Input tokens:
{json_tokens}

Return this exact JSON structure:
{
  "opening_line": "One sentence about style/medium/quality",
  "subject_description": "1-2 sentences about the main subject",
  "appearance_details": "2-3 sentences about physical appearance and clothing",
  "action_and_scene": "1-2 sentences about pose and environment",
  "technical_notes": "1 sentence about lighting/composition (or null if unremarkable)",
  "full_description": "All above combined into flowing paragraphs"
}

Output:

{
  "opening_line": "A sharp, photo-realistic image with a minimalist modern aesthetic.",
  "subject_description": "The photograph features a young woman in her mid-twenties.",
  "appearance_details": "She has long, wavy brown hair and brown eyes that meet the camera directly. Her expression is neutral with slightly parted lips, and she has a fair complexion. Her slim, curvy figure is dressed in a white Calvin Klein crop top with matching white briefs.",
  "action_and_scene": "She stands confidently with arms relaxed at her sides, facing the viewer against a plain light grey studio background.",
  "technical_notes": "Soft, even lighting with neutral tones complements the centered, cowboy-shot composition.",
  "full_description": "A sharp, photo-realistic image with a minimalist modern aesthetic. The photograph features a young woman in her mid-twenties with long, wavy brown hair and brown eyes that meet the camera directly. Her expression is neutral with slightly parted lips, and she has a fair complexion. Her slim, curvy figure is dressed in a white Calvin Klein crop top with matching white briefs. She stands confidently with arms relaxed at her sides, facing the viewer against a plain light grey studio background. Soft, even lighting with neutral tones complements the centered, cowboy-shot composition."
}

Benefits:

  • Guaranteed structure for downstream processing
  • Can use individual fields for different purposes
  • Easy validation
  • Enables A/B testing of different sections

Use templates for structure, LLM for natural phrasing within slots:

Template:

"{style_sentence}

{subject_intro} with {hair_description} and {eye_description}. {face_body_description}. {clothing_description}.

{action_scene_description}.

{lighting_composition_description}."

LLM Calls (Small, Focused):

Call 1: "Convert these hair tokens to a natural phrase: ['long hair', 'wavy hair', 'brown hair']"
→ "long, wavy brown hair"

Call 2: "Convert these clothing tokens to a natural sentence: ['white crop top', 'white panties', 'Calvin Klein']"
→ "She wears a white Calvin Klein crop top with matching white briefs"

... etc

Assembly:

description = TEMPLATE.format(
    style_sentence=llm_style_output,
    subject_intro=llm_subject_output,
    hair_description=llm_hair_output,
    eye_description=llm_eye_output,
    # ... etc
)

Benefits:

  • Controlled overall structure
  • Natural language within each slot
  • Cheaper (smaller prompts)
  • Parallelizable
  • Cacheable at slot level

Model Selection

Model Speed Quality Cost Best For
Claude 3.5 Haiku Fast Good Low High volume
Claude 3.5 Sonnet Medium Excellent Medium Quality focus
GPT-4o-mini Fast Good Low High volume
GPT-4o Medium Excellent High Quality focus
Llama 3 8B (local) Fast Decent Free Cost-sensitive
Mistral 7B (local) Fast Decent Free Cost-sensitive
Qwen 2.5 7B (local) Fast Good Free Cost-sensitive

Recommendation by Volume

Daily Volume Recommended Approach
< 1,000 Single comprehensive prompt, Sonnet/GPT-4o
1,000 - 10,000 Section-by-section, Haiku/GPT-4o-mini
10,000 - 100,000 Hybrid template + LLM slots, Haiku
> 100,000 Rule-based primary, LLM for edge cases only

Prompt Engineering Best Practices

1. Token Normalization Before LLM

BEFORE sending to LLM:
- Remove duplicate tokens
- Sort by confidence (highest first)
- Limit to top N per category (prevent overload)
- Normalize formats (1girl → "woman", etc.)

2. Negative Instructions

DO NOT:
- Add information not present in the tokens
- Use phrases like "the image shows" repeatedly
- List attributes with bullet points
- Include technical tags verbatim (like "1girl" or "cowboy shot")
- Speculate about context, story, or narrative
- Add emotional interpretations unless expression tokens indicate it

3. Style Calibration

TONE: Professional, neutral, descriptive
VOICE: Third person, present tense
LENGTH: 100-200 words (adjust as needed)
VOCABULARY: Art/photography terminology appropriate
AVOID: Flowery language, excessive adjectives, repetition

4. Temperature Settings

Use Case Temperature
Consistent output 0.0 - 0.3
Slight variation 0.4 - 0.6
Creative variety 0.7 - 1.0

Recommendation: 0.1 - 0.3 for reproducible prompt generation.

5. Confidence Integration

Include confidence in prompt for prioritization:

High confidence (>0.9): ["1girl", "brown hair", "standing"]
Medium confidence (0.7-0.9): ["wavy hair", "slim"]
Lower confidence (0.5-0.7): ["curvy", "parted lips"]

Prioritize high-confidence tokens. Include medium-confidence if relevant.
Mention lower-confidence details only if they add value.

Output Validation

Schema Validation

def validate_output(text):
    checks = {
        "min_length": len(text) > 50,
        "max_length": len(text) < 1000,
        "no_raw_tags": not re.search(r'\b1girl\b|\bcowboy shot\b', text),
        "has_sentences": text.count('.') >= 2,
        "no_bullets": '•' not in text and '- ' not in text,
        "no_headers": not re.search(r'^#+\s', text, re.MULTILINE),
    }
    return all(checks.values()), checks

Semantic Validation

def semantic_check(input_tokens, output_text):
    # Check key elements are mentioned
    required_elements = []

    if "brown hair" in input_tokens["Subject Details"]["hair"]:
        required_elements.append(("brown" in output_text and "hair" in output_text))

    if "standing" in input_tokens["Action/Pose"]:
        required_elements.append("stand" in output_text.lower())

    return all(required_elements)

Caching Strategy

┌─────────────────────────────────────────────────────────────────┐
│  Level 1: Full token hash → Complete description                 │
│           (Exact match cache)                                    │
│                                                                  │
│  Level 2: Section token hash → Section text                      │
│           (Partial reuse)                                        │
│                                                                  │
│  Level 3: Common phrase cache                                    │
│           "long wavy brown hair" → "long, wavy brown hair"       │
└─────────────────────────────────────────────────────────────────┘

Cache Key Generation

def generate_cache_key(tokens):
    # Normalize and sort for consistent hashing
    normalized = {
        category: sorted(set(token.lower().strip() for token in items))
        for category, items in tokens.items()
    }
    return hashlib.md5(json.dumps(normalized, sort_keys=True).encode()).hexdigest()

Error Handling

Fallback Chain

FALLBACK CHAIN:
1. Retry with backoff (up to 3 attempts)
2. Try alternative model (Haiku → local Llama)
3. Fall back to rule-based assembly
4. Return structured tokens as-is (worst case)

Error Types

ERROR TYPES:
├── Rate Limit
│   → Exponential backoff, queue system
│
├── Timeout (>30s)
│   → Reduce token count, simpler prompt
│
├── Content Filter Triggered
│   → Log, fallback to rule-based
│
├── Invalid JSON (structured output)
│   → Retry with stricter prompt, parse fallback
│
└── Semantic Validation Failed
    → Retry once, then rule-based fallback

Content Safety Handling

MITIGATION:
- Pre-filter tokens before sending to LLM
- Use less restrictive models for certain content
- Have rule-based fallback for filtered requests

Cost Estimation

Per-Image Cost (Approximate)

Approach Input Tokens Output Tokens Cost (Claude Haiku)
Single prompt ~500 ~200 $0.0002
Section-by-section (5 calls) ~300 total ~200 total $0.0002
Hybrid (10 slot calls) ~200 total ~100 total $0.00012

At Scale

Daily Volume Monthly Cost (Haiku) Monthly Cost (Sonnet)
1,000 ~$6 ~$45
10,000 ~$60 ~$450
100,000 ~$600 ~$4,500
1,000,000 ~$6,000 ~$45,000

Typical ComfyUI User (50-200 images/day)

Approach Daily Cost Monthly Cost
Haiku (single prompt) $0.01 - $0.04 $0.30 - $1.20
GPT-4o-mini $0.01 - $0.05 $0.30 - $1.50
Local Qwen 7B $0 (GPU time only) $0

Hybrid Decision: LLM vs Rule-Based

IF all tokens have confidence > 0.8:
    Use rule-based (predictable inputs)
ELSE IF any category has unusual/low-confidence tokens:
    Use LLM (handles edge cases better)

Mode Selection in Application

MODE = "Standard"      → Rule-based assembly (Phase 5)
MODE = "Enhance with AI" → LLM assembly (Phase 6)

Batch Processing for High Volume

For Section-by-Section approach with 5 calls per image:

DON'T: 5 sequential API calls per image

DO: Batch all section calls across N images
    - Collect 100 images
    - Send all "Subject" sections in one batch
    - Send all "Clothing" sections in one batch
    - Recombine results

This reduces API overhead significantly.


Prompt Versioning

PROMPT_VERSIONS = {
    "v1.0": original_prompt,
    "v1.1": improved_prompt,
    "v2.0": major_revision
}

# Track which version generated each output
metadata["prompt_version"] = "v1.1"

Essential for debugging and A/B testing.


Comparison Summary: Rule-Based vs LLM Assembly

Aspect Rule-Based (Phase 5) LLM (Phase 6)
Output Quality Good, predictable Excellent, natural
Variety Limited by templates High variety
Edge Cases May fail gracefully Handles well
Speed Very fast (<10ms) Slower (500ms-3s)
Cost Free API costs
Consistency Deterministic Variable
Maintenance Template updates Prompt tuning
Dependencies None (pure Python) API/GPU required

Phase 7: Token Grounding Visualization (Optional)

Overview

This phase provides visual debugging and QA capabilities by showing WHERE in the image each token/tag spatially corresponds. This is useful for:

  • Validating classification accuracy
  • Debugging token extraction
  • Understanding model attention
  • User-facing visualization features

Priority: LOW (implement after core pipeline is complete)


Grounding Approaches

Approach 1: CLIP-Based Attention Heatmaps

Concept: CLIP encodes images and text into shared embedding space. Extract attention maps to see where the model "looks" for each token.

Image → CLIP Vision Encoder → Attention Maps (per patch)
Token → CLIP Text Encoder → Text Embedding

For each token:
    Compute similarity between text embedding and each image patch
    → Heatmap of patch-level similarities

Methods:

  • Direct patch similarity
  • GradCAM on CLIP similarity score

Tools: CLIP-ViT, pytorch-grad-cam

Pros Cons
Fast (~20ms GPU) Low spatial precision
Works for abstract concepts Gradient-based approximation
Minimal overhead Noisy for small regions

Best For: Abstract tokens (lighting, style, quality) that can't be precisely localized.


Approach 2: Grounding DINO + SAM

Concept: Two-stage approach - Grounding DINO finds bounding boxes for text queries, SAM refines to precise masks.

┌─────────────────────────────────────────────────────────────────┐
│  Token: "white crop top"                                         │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  GROUNDING DINO                                          │    │
│  │  Input: Image + "white crop top"                         │    │
│  │  Output: Bounding box [(x1,y1), (x2,y2)]                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  SAM (Segment Anything)                                  │    │
│  │  Input: Image + Box prompt                               │    │
│  │  Output: Precise segmentation mask                       │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Token → Prompt Conversion:

Category Token Grounding Prompt
Clothing "white crop top" "white crop top"
Hair "brown hair" "hair"
Environment "grey background" "background"
Action/Pose "arms at sides" "arms"
Pros Cons
Precise segmentation Slower (two models)
State-of-the-art accuracy ~1.1GB model overhead
Handles complex scenes Abstract concepts can't be grounded

Best For: Objects, clothing, body parts, props.


Approach 3: Semantic Segmentation + Token Mapping

Concept: Use human parsing model to get pixel-level labels, map tokens to segment classes.

┌─────────────────────────────────────────────────────────────────┐
│  HUMAN PARSING MODEL (SCHP / Graphonomy)                        │
│                                                                  │
│  Input: Image                                                    │
│  Output: Per-pixel class labels                                  │
│                                                                  │
│  Classes: hair, face, upper_clothes, lower_clothes, arms,       │
│           legs, shoes, background, skin, etc.                    │
└─────────────────────────────────────────────────────────────────┘

Token → Segment Class Mapping:

TOKEN_TO_SEGMENT = {
    # Hair tokens
    "long hair": "hair",
    "brown hair": "hair",
    "wavy hair": "hair",

    # Face tokens
    "brown eyes": "eyes",
    "lips": "lips",
    "parted lips": "lips",

    # Body tokens
    "navel": "torso",
    "breasts": "torso",
    "arms at sides": "arms",

    # Clothing tokens
    "white crop top": "upper_clothes",
    "white panties": "lower_clothes",
    "bra": "upper_clothes",

    # Background
    "grey background": "background",
}
Pros Cons
Very precise for known classes Limited to predefined classes
Fast inference (~50ms) Requires class mapping dictionary
Consistent results Can't handle novel concepts

Best For: Subject Details subcategories (hair, clothing, body, face).


Approach 4: Florence-2 Grounding

Concept: Florence-2 has built-in grounding - can output bounding boxes for concepts it describes.

Florence-2 Tasks:
├── <CAPTION_TO_PHRASE_GROUNDING>
│   Input: "brown hair, white top, standing"
│   Output: Bounding boxes for each phrase
│
├── <REFERRING_EXPRESSION_SEGMENTATION>
│   Input: "the woman's hair"
│   Output: Segmentation mask
│
└── <OPEN_VOCABULARY_DETECTION>
    Input: "crop top"
    Output: Detection boxes
Pros Cons
Already loaded for Analysis Grounding quality varies
Multi-task capable Not all variants support grounding
Zero additional models Box-level (not mask)

Best For: Leveraging existing Florence-2 model with minimal overhead.


def select_grounding_method(token, category):
    # Subject Details - use parsing models
    if category == "Subject Details":
        subcategory = token.metadata.get("subcategory")
        if subcategory in ["hair", "face", "body", "clothing"]:
            return "human_parsing"
        else:
            return "grounding_dino"

    # Environment - use scene parsing or grounding
    elif category == "Environment/Scene":
        if "background" in token.text:
            return "scene_parsing"
        else:
            return "grounding_dino"

    # Action/Pose - use pose estimation
    elif category == "Action/Pose":
        return "pose_estimation"

    # Abstract concepts (can't ground spatially)
    elif category in ["Lighting", "Style/Medium", "Quality Boosters"]:
        return "clip_attention"  # or skip

    # Default fallback
    else:
        return "grounding_dino"

Visualization Strategies

1. Single Token Heatmap

Overlay mask/heatmap for one token at a time.

┌─────────────────────────────────────────────────────────────────┐
│  Image with "brown hair" highlighted                             │
│                                                                  │
│        ████████████                                              │
│       ██ HAIR MASK ██                                            │
│        ████████████                                              │
│                                                                  │
│         [Person]                                                 │
│                                                                  │
│  Token: brown hair (0.915)                                       │
│  Category: Subject Details > Hair                                │
│  Source: wd14                                                    │
└─────────────────────────────────────────────────────────────────┘

2. Multi-Token Composite

Color-coded overlays for multiple tokens, grouped by category.

Color Scheme:
├── Hair tokens: Yellow
├── Clothing tokens: Blue
├── Face tokens: Pink
├── Body tokens: Orange
├── Environment: Green
└── Action/Pose: Purple

3. Interactive HTML Viewer

<div id="viewer">
    <img src="base_image.png" id="base">
    <div class="overlay" data-token="brown_hair">
        <img src="mask_hair.png">
    </div>
</div>

<div id="token-list">
    <label><input type="checkbox" data-token="brown_hair"> brown hair (0.91)</label>
    <label><input type="checkbox" data-token="crop_top"> white crop top (0.87)</label>
</div>

<div id="category-filter">
    [All] [Subject] [Details] [Environment] [Action]
</div>

Handling Non-Groundable Tokens

Some tokens can't be spatially grounded:

Category Examples Strategy
Quality Boosters "realistic", "sharp", "4k" Global indicator (badge/icon)
Style/Medium "photograph", "minimalist" Global indicator
Lighting "soft light", "high key" Whole-image tint or skip
Composition "centered", "rule of thirds" Grid overlay
Technical "portrait orientation" Frame indicator

Output Formats

1. Static Image Export

Output files:
├── image_original.png
├── image_heatmap_hair.png
├── image_heatmap_clothing.png
├── image_heatmap_combined.png
└── image_with_legend.png

2. JSON Annotation Format

{
    "image": "image_001.jpg",
    "dimensions": [512, 288],
    "token_regions": [
        {
            "token": "brown hair",
            "category": "Subject Details > Hair",
            "confidence": 0.915,
            "source": "wd14",
            "region": {
                "type": "mask",
                "rle": "...",
                "bbox": [120, 50, 280, 180],
                "area": 12500,
                "centroid": [200, 115]
            },
            "grounding_method": "human_parsing",
            "grounding_confidence": 0.92
        }
    ],
    "ungrounded_tokens": [
        {"token": "realistic", "reason": "abstract_concept"},
        {"token": "soft light", "reason": "global_attribute"}
    ]
}

Component Tool Purpose Size
Human Parsing SCHP or Graphonomy Body, clothes, hair ~150MB
Face Parsing face-parsing.PyTorch Eyes, lips, nose ~100MB
General Grounding Grounding DINO Objects, props ~700MB
Mask Refinement SAM (ViT-B) Bounding box → mask ~400MB
Fallback CLIP + GradCAM Abstract concepts ~350MB
Visualization matplotlib + OpenCV Overlay composition -

Performance Estimates

Model GPU CPU
SCHP (human parsing) 50ms 500ms
Face parsing 30ms 300ms
Grounding DINO 100ms 2s
SAM (ViT-B) 50ms 1s
CLIP ViT-B/32 20ms 200ms

Full pipeline per image: ~300-500ms (GPU), ~3-5s (CPU)


Integration with Validation

Token grounding can enhance validation (Phase 6) by:

  1. Spatial Coherence Check: Tokens in same category should have overlapping or adjacent regions
  2. Coverage Verification: Subject Details tokens should cover expected body areas
  3. Conflict Detection: Mutually exclusive tokens (standing vs sitting) should have non-overlapping regions
  4. Visual QA Report: Generate annotated images showing classification accuracy

Implementation Priority

This phase is optional and should only be implemented after the core pipeline (Phases 1-6) is complete and validated.

Task Priority Effort
Human parsing integration Medium 4-6 hours
Grounding DINO + SAM Low 6-8 hours
Interactive HTML viewer Low 4-6 hours
JSON export format Medium 2-3 hours
Integration with validation Low 3-4 hours