bump
Browse files- .gitattributes +12 -0
- examples/chroma.jpg +3 -0
- examples/json.jpg +3 -0
- examples/json_comic.jpg +3 -0
- examples/long.jpg +3 -0
- examples/long_thoughts.jpg +3 -0
- examples/long_thoughts_v2.jpg +0 -0
- examples/md_comic.jpg +3 -0
- examples/min_structured_json.jpg +3 -0
- examples/min_structured_md.jpg +3 -0
- examples/short.webp +0 -0
- formats.md +284 -0
- model_BF16.gguf +3 -0
- pics/example.jpg +3 -0
- pics/nsfw_inaccuracy.png +0 -0
- pics/original.jpg +3 -0
- pics/retrieval_from_tags.png +0 -0
- pics/splash.jpg +3 -0
- pics/zero_shot_guess.png +0 -0
- scripts/__pycache__/prompts.cpython-310.pyc +0 -0
- scripts/caption_distributed.py +319 -0
- scripts/gradio_interface.py +635 -0
- scripts/prompts.py +220 -0
- scripts/transformers_ver.py +67 -0
.gitattributes
CHANGED
|
@@ -38,3 +38,15 @@ ToriiGate-0.5_Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
|
| 38 |
mmproj_Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
ToriiGate-0.5_Q4_K_L.gguf filter=lfs diff=lfs merge=lfs -text
|
| 40 |
ToriiGate-0.5_Q6_K_L.gguf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
mmproj_Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
ToriiGate-0.5_Q4_K_L.gguf filter=lfs diff=lfs merge=lfs -text
|
| 40 |
ToriiGate-0.5_Q6_K_L.gguf filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/chroma.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/json_comic.jpg filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
examples/json.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
examples/long_thoughts.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
examples/long.jpg filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
examples/md_comic.jpg filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
examples/min_structured_json.jpg filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
examples/min_structured_md.jpg filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
model_BF16.gguf filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
pics/example.jpg filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
pics/original.jpg filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
pics/splash.jpg filter=lfs diff=lfs merge=lfs -text
|
examples/chroma.jpg
ADDED
|
Git LFS Details
|
examples/json.jpg
ADDED
|
Git LFS Details
|
examples/json_comic.jpg
ADDED
|
Git LFS Details
|
examples/long.jpg
ADDED
|
Git LFS Details
|
examples/long_thoughts.jpg
ADDED
|
Git LFS Details
|
examples/long_thoughts_v2.jpg
ADDED
|
examples/md_comic.jpg
ADDED
|
Git LFS Details
|
examples/min_structured_json.jpg
ADDED
|
Git LFS Details
|
examples/min_structured_md.jpg
ADDED
|
Git LFS Details
|
examples/short.webp
ADDED
|
formats.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Formats
|
| 2 |
+
|
| 3 |
+
The model supports 9 main format variants. They can be divided into 4 categories:
|
| 4 |
+
|
| 5 |
+
## 1. Detailed Structured Descriptions with Multiple Sections
|
| 6 |
+
|
| 7 |
+
- Long Thoughts
|
| 8 |
+
- Long Thoughts v2
|
| 9 |
+
- Chroma-style
|
| 10 |
+
|
| 11 |
+
These create the most detailed descriptions across various criteria with maximum informativeness. They can later be refactored into captions for training a specific model, or their parts can be used for labeling.
|
| 12 |
+
|
| 13 |
+
The first two are intended for use only with named characters.
|
| 14 |
+
|
| 15 |
+
## 2. Structured Formats for Ready-to-Use Prompts
|
| 16 |
+
|
| 17 |
+
- Minimalistic Structured Markdown
|
| 18 |
+
- Minimalistic Structured Json
|
| 19 |
+
- Json
|
| 20 |
+
|
| 21 |
+
These can be directly used as prompts after minimal processing. The existing structure allows selecting individual parts for individual prompting, or leaving them in their current form.
|
| 22 |
+
|
| 23 |
+
Json is recommended to be parsed into plain text or Markdown.
|
| 24 |
+
|
| 25 |
+
The `Minimalistic Structured Markdown` variant has two additional reasoning sections at the beginning; when using it, they should be excluded with regular expressions. It can only be used with character names.
|
| 26 |
+
|
| 27 |
+
## 3. Comic Descriptions
|
| 28 |
+
|
| 29 |
+
- Markdown Comic
|
| 30 |
+
- Json Comic
|
| 31 |
+
|
| 32 |
+
Both formats are optimized for comic and manga descriptions, creating detailed descriptions of each frame.
|
| 33 |
+
|
| 34 |
+
`Markdown Comic` has a reasoning block at the beginning to improve accuracy.
|
| 35 |
+
|
| 36 |
+
## 4. Legacy Formats
|
| 37 |
+
|
| 38 |
+
- Long
|
| 39 |
+
- Short
|
| 40 |
+
|
| 41 |
+
Long and short descriptions in plain text without formatting and structuring. Support for using names is available.
|
| 42 |
+
|
| 43 |
+
# Examples
|
| 44 |
+
|
| 45 |
+
## Long Thoughts v2
|
| 46 |
+
|
| 47 |
+

|
| 48 |
+
|
| 49 |
+
> # 1. Thoughts about characters
|
| 50 |
+
> The image features two characters: Shirakami Fubuki and Sukonbu (a personification of Shirakami Fubuki). Shirakami Fubuki is identifiable by her distinct fox ears, white hair with a single braid, fox tail, and ahoge. Sukonbu is a small, chibi-like figure with fox ears, a star accessory on the chest, and a simple, rounded design.
|
| 51 |
+
>
|
| 52 |
+
> # 2. Key details
|
| 53 |
+
> - The main focus is on the shadow cast on the sand, which shows a figure in a blue bikini with fox ears and a tail, making a double fox shadow puppet gesture with both hands.
|
| 54 |
+
> - The shadow is clear and well-defined, contrasting sharply with the sandy background.
|
| 55 |
+
> - The actual character (Fubuki) is not directly visible, only her shadows and a small plush-like Sukonbu figure in the lower left corner.
|
| 56 |
+
> - The shadow hints at a playful and cute moment, referencing the double fox shadow puppet.
|
| 57 |
+
> - The lighting suggests a sunny day with sharp shadows, possibly at sunset or sunrise due to the warm sand tone.
|
| 58 |
+
>
|
| 59 |
+
> # 3. Long description
|
| 60 |
+
> This high-resolution image depicts a sandy beach scene from a birds-eye view. The sand is smooth and uniformly colored in warm beige tones. Dominating the center is a sharp, dark shadow of a fox-eared girl over a blue bikini with a frilled skirt bottom. The shadow shows the character standing on one leg and arms raised, hands forming the classic fox shadow puppet gesture with two fingers extended on each hand. The shadow includes a clearly visible fox tail, fox ears, and an ahoge (a single hair strand sticking up).
|
| 61 |
+
>
|
| 62 |
+
> In the bottom left corner, a small, chibi-style plush or figure resembling Sukonbu, the personification of Shirakami Fubuki, is sitting on the sand. Sukonbu has large black eyes, a small open mouth with a cheerful expression, blue markings on the face, and a star-shaped accessory on its chest. The plushs ears and tail are clearly visible, matching the fox theme.
|
| 63 |
+
>
|
| 64 |
+
> The image cleverly uses the shadow as a focal point, showing the silhouette of Shirakami Fubuki in her blue bikini, while the actual figure is not seen directly, creating a playful and meme-inspired visual. The lighting is bright and casts a crisp shadow on the sand, emphasizing the contours and playful pose of the fox-girl shadow puppet.
|
| 65 |
+
>
|
| 66 |
+
> # 4. Detailed description for each character
|
| 67 |
+
> ## Shirakami Fubuki
|
| 68 |
+
> Though not directly visible, Shirakami Fubukis shadow is the central figure on the sand. The shadow shows her wearing a blue bikini top tied in the center with a bow and a frilled bikini skirt bottom. She has fox ears on top of her head and a prominent fox tail curling behind her. Her arms are raised, and she is making a double fox shadow puppet with her hands. The shadow also shows her signature ahoge on top of her head. Her posture is relaxed, standing with legs apart, and the shadow captures her playful and cute personality.
|
| 69 |
+
>
|
| 70 |
+
> ## Sukonbu (Shirakami Fubuki)
|
| 71 |
+
> Sukonbu appears as a small, chibi plush or figure in the bottom left corner of the image. It has a round, white face with large black oval eyes, a small smiling mouth, and light blue markings on the cheeks and forehead. The plush has large fox ears with black inner parts and a yellow tuft of hair at the back. It wears a blue scarf or ribbon tied around its neck with a star-shaped accessory in the center. The tail is fluffy and visible behind it, matching the fox theme. Sukonbu looks cheerful and adds a cute contrast to the shadow figure.
|
| 72 |
+
|
| 73 |
+
## Long Thoughts
|
| 74 |
+
|
| 75 |
+

|
| 76 |
+
|
| 77 |
+
> # 1. Thoughts about characters
|
| 78 |
+
> The main identifiable character here is Wakamo from Blue Archive, recognized by her distinctive fox ears, large fluffy tail, and long black hair with yellow eyes. The other figure is a roughly sketched man in a suit, likely representing Sensei (Blue Archive), though he is not fully detailed or named.
|
| 79 |
+
>
|
| 80 |
+
> # 2. General description
|
| 81 |
+
> The image is a humorous, stylized cartoon drawing with a simple white background. It features a chibi-styled Wakamo sitting on a small wooden stool, holding a bouquet of yellow flowers. She has large fox ears, a fluffy black and red tail wagging excitedly, and wears a black dress with gold patterns and a red cape. Next to her stands a crudely drawn man in a beige suit, tightly bound in red string, holding a "Marriage Certificate" paper with a red stamp and a checkmark. The mans face is obscured by a scribble, and he smiles awkwardly. In the upper right corner, a small inset photo shows a real-life wedding scene with the same couple tied up in a similar red string bondage, smiling and posing happily.
|
| 82 |
+
>
|
| 83 |
+
> # 3. Detailed description for each character
|
| 84 |
+
> ## Wakamo (Blue Archive)
|
| 85 |
+
> Wakamo is portrayed in a chibi style with exaggerated large yellow eyes and a small open mouth showing a cheerful expression. Her long black hair flows down her back, adorned with a small yellow flower hair ornament on one side. Her fox ears are black with red tips and white inner fur, positioned upright. She wears a black dress with gold cloud-like patterns and a red cape draped over her shoulders. Her large, fluffy fox tail is black with a red tip and is animatedly wagging, indicated by motion lines. She holds a small bouquet of yellow flowers in her gloved hands and sits on a simple wooden stool.
|
| 86 |
+
>
|
| 87 |
+
> ## Sensei (Blue Archive) (sketch)
|
| 88 |
+
> The man next to Wakamo is roughly sketched with minimal detail. He wears a beige suit with a white shirt and a tie, and a small yellow flower is tucked into his suit pocket. His face is obscured by a scribble, but he has a smiling mouth visible. He is tightly bound in red string crossing his torso and arms, holding a paper labeled "Marriage Certificate" with a red stamp and a checkmark. His posture is slightly bent forward, and the strings appear taut, emphasizing the bondage.
|
| 89 |
+
>
|
| 90 |
+
> # 4. Individual Parts
|
| 91 |
+
> 1. Wakamos large fox ears: black with red tips and white inner fur, upright on her head.
|
| 92 |
+
> 2. Wakamos long black hair: flowing behind her with a yellow flower ornament on the left side.
|
| 93 |
+
> 3. Wakamos yellow eyes: large and round with a happy expression.
|
| 94 |
+
> 4. Wakamos black dress: decorated with gold cloud patterns, short and fitted.
|
| 95 |
+
> 5. Wakamos red cape: draped over her shoulders, flowing behind her.
|
| 96 |
+
> 6. Wakamos fluffy fox tail: black with a red tip, wagging energetically with motion lines.
|
| 97 |
+
> 7. Wakamos small bouquet: yellow flowers held in both hands.
|
| 98 |
+
> 8. Wakamos small wooden stool: simple design, supporting her seated position.
|
| 99 |
+
> 9. The mans beige suit: classic cut with a white shirt and tie underneath.
|
| 100 |
+
> 10. Yellow flower in the mans suit pocket: matching the bouquet Wakamo holds.
|
| 101 |
+
> 11. Mans face: scribbled out, obscuring identity, but smiling mouth is visible.
|
| 102 |
+
> 12. Red string bondage: tightly wrapped around the mans torso, arms, and hands.
|
| 103 |
+
> 13. Marriage Certificate paper: held by the man, with red stamp and a checkmark.
|
| 104 |
+
> 14. Motion lines around Wakamos tail: indicating wagging movement.
|
| 105 |
+
> 15. White background: clean and simple, focusing attention on characters.
|
| 106 |
+
> 16. Small inset photo in upper right corner: real-life photo of the same couple tied up in red string, smiling, with wedding attire and bouquet.
|
| 107 |
+
>
|
| 108 |
+
> # 5. Texts on image
|
| 109 |
+
> - On the paper held by the man, the text reads: "Marriage Certificate" in handwritten style.
|
| 110 |
+
> - The paper also features a red stamp and a checkmark symbol.
|
| 111 |
+
>
|
| 112 |
+
> # 6. Background and effects
|
| 113 |
+
> The background is plain white with no additional elements, emphasizing the characters. The main drawing style is cartoonish with simple lines and flat colors. The inset photo in the upper right corner is a real-life photograph showing the same couple tied up in red string bondage, smiling and posing happily in wedding attire with a bouquet. The contrast between the stylized cartoon and the real photo adds a humorous meta-commentary. The image is clear and focused, with no blur or depth of field effects. The tails wagging motion is conveyed through curved motion lines.
|
| 114 |
+
|
| 115 |
+
## Chroma-style
|
| 116 |
+
|
| 117 |
+

|
| 118 |
+
|
| 119 |
+
> ### 1. Regular Summary:
|
| 120 |
+
>
|
| 121 |
+
> This piece is a stunning digital painting, likely done by a professional artist judging by the detail and dynamic composition. The perspective is a dramatic dutch angle, looking up at Rosmontis from Arknights, a catgirl with long grey hair and green eyes, who's wielding a massive, futuristic-looking weapon that looks like a jetpack or some kind of energy cannon. She's dressed in a short, sleeveless white dress with a black jacket draped over her shoulders, black gloves, and black boots. Her cat ears and tail are clearly visible. The background is a chaotic scene of crumbling buildings and debris, suggesting a post-apocalyptic or battle-ravaged city. The color palette is mostly dark greys and blacks, contrasted by the bright blues and reds of the energy effects and weapon. There's a sense of intense action and power; she looks ready to wreck some shit. The overall aesthetic is high fantasy meets sci-fi, with a lot of detail in the character design and the weapon's design. The artist clearly has skill and the work is very high quality.
|
| 122 |
+
>
|
| 123 |
+
> ### 2. Individual Parts:
|
| 124 |
+
>
|
| 125 |
+
> 1. **Rosmontis (Arknights):** The central figure, a catgirl wielding a large weapon. She's positioned slightly off-center, facing towards the viewer.
|
| 126 |
+
> 2. **Large Weapon:** A futuristic-looking energy cannon or jetpack-like device, held by Rosmontis. It's the most prominent object in the image, with glowing blue and red energy effects.
|
| 127 |
+
> 3. **White Dress:** A short, sleeveless white dress worn by Rosmontis.
|
| 128 |
+
> 4. **Black Jacket:** A black jacket draped over Rosmontis's shoulders.
|
| 129 |
+
> 5. **Black Gloves:** Gloves worn by Rosmontis.
|
| 130 |
+
> 6. **Black Boots:** Boots worn by Rosmontis.
|
| 131 |
+
> 7. **Cat Ears:** Cat ears atop Rosmontis's head.
|
| 132 |
+
> 8. **Cat Tail:** A cat tail extending from Rosmontis's back.
|
| 133 |
+
> 9. **Crumbling Buildings:** The background consists of partially destroyed buildings, suggesting a post-apocalyptic setting.
|
| 134 |
+
> 10. **Debris:** Scattered debris and rubble throughout the background.
|
| 135 |
+
> 11. **Energy Effects:** Bright blue and red energy effects surrounding the weapon and Rosmontis, adding to the dynamic feel.
|
| 136 |
+
> 12. **Ground:** The ground is cracked and broken, consistent with the background's overall state of destruction.
|
| 137 |
+
> 13. **Lighting:** The lighting is dramatic, highlighting Rosmontis and the weapon against the darker background.
|
| 138 |
+
> 14. **Perspective:** The image is shot from a low angle, looking up at Rosmontis, creating a sense of power and scale.
|
| 139 |
+
> 15. **Color Palette:** The color palette is dark, with contrasting bright blues and reds.
|
| 140 |
+
>
|
| 141 |
+
>
|
| 142 |
+
> ### 3. Midjourney-Style Summary:
|
| 143 |
+
>
|
| 144 |
+
> Rosmontis, Arknights character, catgirl, wielding massive energy weapon, futuristic design, glowing blue and red energy, short white dress, black jacket, black gloves and boots, cat ears and tail visible, dramatic dutch angle, crumbling city background, post-apocalyptic setting, intense action, high fantasy/sci-fi aesthetic, high-quality artwork, dynamic composition, dark color palette, bright color accents, sense of power and scale, detailed character design, detailed weapon design, professional-level art.
|
| 145 |
+
>
|
| 146 |
+
> ### 4. DeviantArt Commission Request
|
| 147 |
+
>
|
| 148 |
+
> A dynamic action piece featuring Rosmontis from Arknights. She's a catgirl with long grey hair and green eyes, wearing a short, sleeveless white dress and a black jacket. She's wielding a huge, futuristic energy weapon that resembles a jetpack or energy cannon, with glowing blue and red energy effects. The background should be a chaotic scene of a ruined city, with crumbling buildings and debris. The overall style should be high fantasy meets sci-fi, with a focus on dynamic action and detail. The perspective should be a dramatic dutch angle, looking up at Rosmontis from a low angle. The lighting should be dramatic, highlighting Rosmontis and the weapon. The color palette should be mostly dark greys and blacks, with bright blue and red accents. High resolution, please.
|
| 149 |
+
|
| 150 |
+
## Minimalistic Structured Markdown
|
| 151 |
+
|
| 152 |
+

|
| 153 |
+
|
| 154 |
+
> # 1. Thoughts about characters
|
| 155 |
+
> The girl with long white hair tied in a ponytail, pointy elf ears, and wearing a white and gold-trimmed outfit is clearly Frieren. The boy with short blue hair, blue eyes, and a blue cloak is Himmel from Sousou no Frieren.
|
| 156 |
+
>
|
| 157 |
+
> # 2. Key details
|
| 158 |
+
> The two characters are sharing a gentle kiss, eyes closed, standing close to each other in front of a rustic stone and timber house with a red roof. The outdoor setting is bright with a blue sky, scattered clouds, and mountains in the background. The mood is romantic and serene. Petals float softly around them, adding a delicate, dreamy atmosphere.
|
| 159 |
+
>
|
| 160 |
+
> # 3. Structured description
|
| 161 |
+
> ## General
|
| 162 |
+
> The image depicts a romantic moment outdoors, with two characters kissing in front of a traditional stone and wood cottage with a red-tiled roof. The scene is bathed in warm natural light, with a mountain range and scattered clouds visible in the background. The grassy field and stone wall in the foreground add depth and frame the couple.
|
| 163 |
+
>
|
| 164 |
+
> ## Frieren
|
| 165 |
+
> Frieren stands on the left, eyes closed, leaning in for the kiss. She has long white hair pulled into a ponytail, pointed elf ears, and wears a white dress with gold trim and a matching capelet. Her expression is peaceful and tender.
|
| 166 |
+
>
|
| 167 |
+
> ## Himmel (Sousou no Frieren)
|
| 168 |
+
> Himmel stands on the right, also with eyes closed, reciprocating the kiss. He has short blue hair, a blue cloak with a high collar, and a black undershirt visible beneath. His posture is gentle and affectionate.
|
| 169 |
+
>
|
| 170 |
+
> ## Image effects
|
| 171 |
+
> The image has a soft, slightly grainy texture reminiscent of traditional animation or painted art. The lighting is warm and natural, casting gentle shadows and highlights that emphasize the characters and the background scenery. Petals floating in the air add a subtle dynamic element.
|
| 172 |
+
|
| 173 |
+
## Minimalistic Structured Json
|
| 174 |
+
|
| 175 |
+

|
| 176 |
+
|
| 177 |
+
> {
|
| 178 |
+
>
|
| 179 |
+
> "General": "Three girls from Nijisanji pose closely together against a plain white background, each making a distinct hand sign. The image is brightly lit and sharply detailed, focusing on their upper bodies and expressive faces.",
|
| 180 |
+
>
|
| 181 |
+
> "Ange Katrina": "On the left, Ange has short, layered red hair with a small gold triangular hairclip. Her blue eyes and slightly parted lips show a gentle, curious expression with a faint blush. She wears a red jacket with wide white cuffs over a high-collared white shirt adorned with a large blue bow and gold triangular decorations. Her right hand forms a peace sign near her face.",
|
| 182 |
+
>
|
| 183 |
+
> "Lize Helesta": "Centered, Lize has long white hair with blue streaks and blunt bangs, accented by a blue feather hair ornament on the left side. Her purple eyes and confident, slightly smug smile with a blush accompany her raised hands making a double 'rock on' gesture. She wears a white blouse with a high collar, a large blue bow, and frilled blue cuffs under a sleeveless dark vest.",
|
| 184 |
+
>
|
| 185 |
+
> "Inui Toko": "On the right, Inui sports long dark brown hair styled in low twintails with black dog ears topped by a cream maid headdress. Her heterochromia features one yellow and one red eye, and she has a small red flower hairpin. She wears a black kimono with wide sleeves, a white frilled collar, and a blue obi tied at the back. A small black-and-white dog plush or accessory peeks behind her shoulder. She makes a peace sign with her left hand, her mouth open slightly revealing a small fang, and her cheeks flushed."
|
| 186 |
+
>
|
| 187 |
+
> }
|
| 188 |
+
|
| 189 |
+
## Json
|
| 190 |
+
|
| 191 |
+

|
| 192 |
+
|
| 193 |
+
> {
|
| 194 |
+
>
|
| 195 |
+
> "character_1": "Hoshimi Miyabi is depicted sleeping upright on a couch, her head resting on the shoulder of another girl. She has long black hair with fox ears, and her attire consists of a white shirt, a black necktie, and a dark grey pleated skirt. Her expression is peaceful, her eyes closed. She's wearing black thigh-high stockings with a lace trim at the top.",
|
| 196 |
+
>
|
| 197 |
+
> "character_2": "Belle is sitting on the couch, holding a smartphone in her hands. She has short, blue hair and green eyes. She's wearing a dark grey long-sleeved shirt with Japanese text on it, and a black skirt. Her expression is calm and she's looking at the phone. She's wearing black thigh-high stockings.",
|
| 198 |
+
>
|
| 199 |
+
> "background": "The setting appears to be an indoor space, possibly a living room or common area. The background includes a brick wall, a chalkboard, a small potted cactus on a table, and various other indistinct objects. The couch is orange and has cushions. The overall lighting suggests an evening or nighttime setting.",
|
| 200 |
+
>
|
| 201 |
+
> "image_effects": "The image has a soft, slightly muted color palette, giving it a calm and relaxed atmosphere. The style is consistent with modern anime artwork.",
|
| 202 |
+
>
|
| 203 |
+
> "texts": "None",
|
| 204 |
+
>
|
| 205 |
+
> "atmosphere": "The overall atmosphere is intimate and peaceful. The scene depicts a quiet moment of companionship between the two girls, with a sense of calm and relaxation. The soft lighting and muted colors contribute to this feeling."
|
| 206 |
+
>
|
| 207 |
+
> }
|
| 208 |
+
|
| 209 |
+
## Markdown Comic
|
| 210 |
+
|
| 211 |
+

|
| 212 |
+
|
| 213 |
+
> # 1. Thoughts about characters
|
| 214 |
+
> The three girls depicted are clearly identifiable based on the given tags and their visual traits. The girl with brown hair, blue eyes, long hair, rabbit ears, and wearing a black jacket with a blue scarf is Amiya (Arknights). The girl with short pinkish hair, blue eyes, fox ears, and a white jacket with a red cross is Sussurro (Arknights). The blonde-haired girl with yellow eyes, fox ears, a blue hairband, and a white and black outfit is Suzuran (Arknights).
|
| 215 |
+
>
|
| 216 |
+
> # 2. Key details
|
| 217 |
+
> - The comic humorously contrasts the characters' appearances at two different ages: "In 1097 years" and "In 1102 years."
|
| 218 |
+
> - The first two frames show the characters standing side by side with neutral expressions.
|
| 219 |
+
> - The third frame zooms in on Sussurro’s face with a serious, slightly annoyed expression against a black background, emphasizing her mood.
|
| 220 |
+
> - The final frame shows Sussurro drinking milk directly from a carton, with a speech bubble saying "too late."
|
| 221 |
+
> - A mysterious hooded figure with a milk carton stands in the background of the last frame, adding to the scene’s humor.
|
| 222 |
+
>
|
| 223 |
+
> # 3. Comic format
|
| 224 |
+
> The comic is a 4koma (four-panel comic) arranged in a 2x2 grid. The characters Amiya, Sussurro, and Suzuran (all Arknights operators) appear in all frames except the third, which is a close-up of Sussurro alone.
|
| 225 |
+
>
|
| 226 |
+
> # 4. Details for each frame
|
| 227 |
+
> ## 4.1 Frame 1 (top-left)
|
| 228 |
+
> Three girls stand side by side against a white background with the text "In 1097 years" above them. From left to right: Amiya, Sussurro, and Suzuran. Amiya has long brown hair, rabbit ears, a black jacket with blue highlights, and a blue scarf. Sussurro has short pinkish hair, fox ears, and wears a white jacket with a red cross on the sleeve and a black choker. Suzuran has long blonde hair, fox ears, a blue hairband, and a white and black dress with a skirt. All three have neutral, slightly serious expressions.
|
| 229 |
+
>
|
| 230 |
+
> ## 4.2 Frame 2 (top-right)
|
| 231 |
+
> The same three characters appear again, but the text above reads "In 1102 years." They look slightly older and more mature. Amiya’s hair is longer, and she wears a sleeveless white top with a blue skirt and her jacket hanging off her shoulders. Sussurro’s expression is unchanged, still serious. Suzuran looks more mature with a frilled collar and a more elaborate outfit, standing with her hands on her hips. The background remains white.
|
| 232 |
+
>
|
| 233 |
+
> ## 4.3 Frame 3 (bottom-left)
|
| 234 |
+
> A close-up of Sussurro’s face fills the frame against a black background. She has a slightly annoyed, sweat-dropping expression with narrowed blue eyes. Her fox ears are prominent, and her pinkish hair with an ahoge (hair antenna) curls upward. She wears her white jacket with the red cross visible on the sleeve.
|
| 235 |
+
>
|
| 236 |
+
> ## 4.4 Frame 4 (bottom-right)
|
| 237 |
+
> Sussurro stands drinking milk directly from a carton, tilting her head back with closed eyes and a satisfied expression. She wears a sleeveless blue dress with a black collar and a black choker. Her fox ears and tail are visible. Next to her is a hooded figure (possibly a doctor or nurse) standing silently with a milk carton in hand. On the table in front of them is a bowl of cereal or some dry food and a small white container. Sussurro’s speech bubble says "too late."
|
| 238 |
+
>
|
| 239 |
+
> # 5. Extra comment
|
| 240 |
+
> The comic uses a simple and clean art style with clear linework and soft colors. The humor revolves around the passage of time and Sussurro’s stubborn or impatient attitude, culminating in her drinking milk in a somewhat casual, relaxed manner despite the serious tone of the previous frames. The mysterious hooded figure adds an extra layer of comedic mystery to the final panel.
|
| 241 |
+
|
| 242 |
+
## Json Comic
|
| 243 |
+
|
| 244 |
+

|
| 245 |
+
|
| 246 |
+
> {
|
| 247 |
+
>
|
| 248 |
+
> "comic_format": "Comic of 2 frames",
|
| 249 |
+
>
|
| 250 |
+
> "1st_frame": "A man with brown hair and stubble is shown in a state of shock. He is wearing a jumpsuit with a logo on the chest and holding a blue stuffed toy. The door he is standing next to is broken, and there is a pregnancy test visible in the foreground. There is a poster on the wall with a character and the text 'Ha Ha Yee'.",
|
| 251 |
+
>
|
| 252 |
+
> "2nd_frame": "A girl with black hair and black eyes is peeking through the broken door. She is smiling and looking at the man with a playful expression. She has a striped shirt visible under her jacket. The text 'Here's Mommy!' is displayed below her.",
|
| 253 |
+
>
|
| 254 |
+
> "character_1": "The man appears surprised and anxious, with facial hair and wearing a jumpsuit. He is holding a stuffed toy.",
|
| 255 |
+
>
|
| 256 |
+
> "character_2": "The girl has a mischievous smile, black hair, and is wearing a striped shirt under a jacket. She seems to be the source of the man's surprise.",
|
| 257 |
+
>
|
| 258 |
+
> "texts": "The text 'Here's Mommy!' is present in the second frame.",
|
| 259 |
+
>
|
| 260 |
+
> "meaning": "The comic parodies a scene from 'The Shining,' with a humorous twist involving a pregnancy test and the unexpected appearance of the girl."
|
| 261 |
+
>
|
| 262 |
+
> }
|
| 263 |
+
|
| 264 |
+
## Long
|
| 265 |
+
|
| 266 |
+

|
| 267 |
+
|
| 268 |
+
> The image features two characters from "Boku no Hero Academia," Todoroki Shouto and Bakugou Katsuki, standing back-to-back against a vibrant green background.
|
| 269 |
+
>
|
| 270 |
+
> Todoroki Shouto is on the left. He has striking multicolored hair, split between white and red, and heterochromia with one blue eye and one grey. His expression is calm, with a closed mouth and a slight smile. He wears a stylish letterman jacket with a floral print, showcasing intricate red and orange flowers. The jacket's sleeves are long, and he has a relaxed posture, with his head slightly tilted.
|
| 271 |
+
>
|
| 272 |
+
> On the right is Bakugou Katsuki, identifiable by his spiky blonde hair and intense red eyes. His expression is more animated, with an open mouth revealing sharp teeth, conveying a sense of determination or frustration. He is also wearing a letterman jacket, but with a different floral design featuring subtle white and grey flowers. His body language is assertive, leaning slightly forward.
|
| 273 |
+
>
|
| 274 |
+
> The background is a simple, solid green that makes the characters stand out prominently. The text "Todoroki & Bakugou" is boldly displayed in white, adding a dynamic element to the composition. The overall atmosphere is energetic and vibrant, capturing the contrasting personalities of the two characters.
|
| 275 |
+
|
| 276 |
+
## Short
|
| 277 |
+
|
| 278 |
+

|
| 279 |
+
|
| 280 |
+
> The image features a chibi-style girl, Artoria Pendragon (Fate), with short, pale blonde hair in a bob, small braids, and striking yellow eyes. Her expression is serious, with a slight blush on her cheeks. She wears a dark purplish-grey dress with a low-cut neckline, revealing cleavage, and long sleeves with white detailing. A dark teal pleated skirt is visible underneath, along with black pantyhose and shoes. She holds a black sword with red markings in her right hand, ready for action. The background is a dark bluish-grey with dynamic red streaks, suggesting motion. The overall atmosphere is intense and dramatic, enhanced by the chibi art style. The artist's signature 'Yui2' is in the upper right corner.
|
| 281 |
+
|
| 282 |
+
---
|
| 283 |
+
|
| 284 |
+
Images belong to their authors and are used exclusively as examples.
|
model_BF16.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:061ffdbe2656986c014044941be9e259c76c5f1482cde7b855bfc1e8382afe36
|
| 3 |
+
size 9695791968
|
pics/example.jpg
ADDED
|
Git LFS Details
|
pics/nsfw_inaccuracy.png
ADDED
|
pics/original.jpg
ADDED
|
Git LFS Details
|
pics/retrieval_from_tags.png
ADDED
|
pics/splash.jpg
ADDED
|
Git LFS Details
|
pics/zero_shot_guess.png
ADDED
|
scripts/__pycache__/prompts.cpython-310.pyc
ADDED
|
Binary file (9.89 kB). View file
|
|
|
scripts/caption_distributed.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import base64
|
| 3 |
+
import requests
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Dict, Any, Optional
|
| 6 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 7 |
+
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
# Import prompt building functions from prompts.py
|
| 12 |
+
from prompts import make_user_query, system_prompt, prompts_b
|
| 13 |
+
|
| 14 |
+
# ==================== CONFIGURATION ====================
|
| 15 |
+
|
| 16 |
+
# Captioning type (from prompts_b in prompts.py)
|
| 17 |
+
C_TYPE = 'long_thoughts_v2'
|
| 18 |
+
if C_TYPE not in prompts_b:
|
| 19 |
+
raise(f"{C_TYPE} not found in known formats!")
|
| 20 |
+
|
| 21 |
+
# Content options
|
| 22 |
+
USE_NAMES = True
|
| 23 |
+
ADD_TAGS = False
|
| 24 |
+
ADD_CHAR_LIST = False
|
| 25 |
+
ADD_CHARS_TAGS = False
|
| 26 |
+
ADD_CHARS_DESCR = False
|
| 27 |
+
|
| 28 |
+
# Grounding requires image folder to contain JSON files with the same name with following format:
|
| 29 |
+
# {
|
| 30 |
+
# "tags": [], # list of strings with tags
|
| 31 |
+
# "characters": [], # list of strings with character tags/names
|
| 32 |
+
# "char_p_tags": {"chars": {"Albedo": "girl", "horns", "black_hair",...}, "skins": {}},
|
| 33 |
+
# "char_descr": {"chars": {"Albedo": "Albedo is a curvy woman with..."}}, "skins": {}}
|
| 34 |
+
# }
|
| 35 |
+
|
| 36 |
+
# Output settings
|
| 37 |
+
SUFFIX = "_lsv2_zs.txt"
|
| 38 |
+
|
| 39 |
+
# API settings
|
| 40 |
+
API_URL = "http://127.0.0.1:9001/v1/chat/completions"
|
| 41 |
+
API_KEY = "not-needed" # vllm typically doesn't require auth
|
| 42 |
+
MODEL = "toriigate-0.5" # or your local model name
|
| 43 |
+
|
| 44 |
+
# Processing settings
|
| 45 |
+
INPUT_FOLDER = "/path/to/files"
|
| 46 |
+
#OUTPUT_FOLDER = "/path/to/output"
|
| 47 |
+
OUTPUT_FOLDER = INPUT_FOLDER
|
| 48 |
+
|
| 49 |
+
# Thread pool settings
|
| 50 |
+
NUM_WORKERS = 16
|
| 51 |
+
|
| 52 |
+
# Image settings
|
| 53 |
+
MAX_PIXELS = 1.0 # Maximum resolution in megapixels (e.g., 1.0 = 1MP)
|
| 54 |
+
|
| 55 |
+
# Request settings
|
| 56 |
+
MAX_TOKENS = 2048
|
| 57 |
+
TEMPERATURE = 0.5
|
| 58 |
+
REQUEST_TIMEOUT = 60 # seconds
|
| 59 |
+
|
| 60 |
+
# ==================== END CONFIGURATION ====================
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def encode_image_base64(image_path: str, max_pixels: float = MAX_PIXELS) -> str:
|
| 64 |
+
"""Encode image to base64 string, resizing if necessary."""
|
| 65 |
+
img = Image.open(image_path)
|
| 66 |
+
|
| 67 |
+
# Check if resizing needed
|
| 68 |
+
current_pixels = img.width * img.height
|
| 69 |
+
max_pixels_count = max_pixels * 1_000_000
|
| 70 |
+
|
| 71 |
+
if current_pixels <= max_pixels_count:
|
| 72 |
+
# No resize needed
|
| 73 |
+
if img.mode != 'RGB':
|
| 74 |
+
img = img.convert('RGB')
|
| 75 |
+
with open(image_path, "rb") as f:
|
| 76 |
+
return base64.b64encode(f.read()).decode("utf-8")
|
| 77 |
+
|
| 78 |
+
# Calculate new dimensions while preserving aspect ratio
|
| 79 |
+
scale = (max_pixels_count / current_pixels) ** 0.5
|
| 80 |
+
new_width = int(img.width * scale)
|
| 81 |
+
new_height = int(img.height * scale)
|
| 82 |
+
|
| 83 |
+
# Resize with high quality
|
| 84 |
+
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
| 85 |
+
if img.mode != 'RGB':
|
| 86 |
+
img = img.convert('RGB')
|
| 87 |
+
|
| 88 |
+
# Encode resized image to base64
|
| 89 |
+
import io
|
| 90 |
+
buffer = io.BytesIO()
|
| 91 |
+
img.save(buffer, format='JPEG', quality=95)
|
| 92 |
+
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def load_json_item(json_path: Optional[Path]) -> tuple[Optional[Dict[str, Any]], bool]:
|
| 96 |
+
"""
|
| 97 |
+
Load JSON metadata from file.
|
| 98 |
+
Returns (data, was_loaded) tuple. If file missing/None, returns (empty_template, False).
|
| 99 |
+
"""
|
| 100 |
+
empty_template = {
|
| 101 |
+
"tags": [],
|
| 102 |
+
"characters": [],
|
| 103 |
+
"char_p_tags": {"chars": {}, "skins": {}},
|
| 104 |
+
"char_descr": {"chars": {}, "skins": {}}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
if json_path is None or not json_path.exists():
|
| 108 |
+
#print(f"[WARN] JSON file not found: {json_path.name if json_path else 'N/A'}")
|
| 109 |
+
return empty_template, False
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
with open(json_path, "r", encoding="utf-8") as f:
|
| 113 |
+
return json.load(f), True
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print(f"[ERROR] Failed to load {json_path}: {e}")
|
| 116 |
+
return empty_template, False
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def find_image_path(image_name: str, folder: Path) -> Optional[Path]:
|
| 120 |
+
"""Find image file with given name (supports jpg, png, etc.)."""
|
| 121 |
+
extensions = ['.jpg', '.jpeg', '.png', '.webp', '.bmp']
|
| 122 |
+
for ext in extensions:
|
| 123 |
+
path = folder / f"{image_name}{ext}"
|
| 124 |
+
if path.exists():
|
| 125 |
+
return path
|
| 126 |
+
return None
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def find_json_path(image_name: str, folder: Path) -> Optional[Path]:
|
| 130 |
+
"""Find JSON file with given name."""
|
| 131 |
+
path = folder / f"{image_name}.json"
|
| 132 |
+
return path if path.exists() else None
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def prepare_messages(item: Dict[str, Any], image_data: str) -> list:
|
| 136 |
+
"""Prepare OpenAI-style messages for the API."""
|
| 137 |
+
user_query = make_user_query(
|
| 138 |
+
item,
|
| 139 |
+
c_type=C_TYPE,
|
| 140 |
+
use_names=USE_NAMES,
|
| 141 |
+
add_tags=ADD_TAGS,
|
| 142 |
+
add_characters=ADD_CHAR_LIST,
|
| 143 |
+
add_char_tags=ADD_CHARS_TAGS,
|
| 144 |
+
add_descritpion=ADD_CHARS_DESCR,
|
| 145 |
+
underscores_replace=False
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
return [
|
| 149 |
+
{
|
| 150 |
+
"role": "system",
|
| 151 |
+
"content": [{"type": "text", "text": system_prompt}]
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"role": "user",
|
| 155 |
+
"content": [
|
| 156 |
+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}},
|
| 157 |
+
{"type": "text", "text": user_query}
|
| 158 |
+
]
|
| 159 |
+
}
|
| 160 |
+
]
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def call_caption_api(messages: list) -> Optional[str]:
|
| 164 |
+
"""Call the captioning API (no retries)."""
|
| 165 |
+
payload = {
|
| 166 |
+
"model": MODEL,
|
| 167 |
+
"messages": messages,
|
| 168 |
+
"max_tokens": MAX_TOKENS,
|
| 169 |
+
"temperature": TEMPERATURE,
|
| 170 |
+
"stream": False
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
headers = {
|
| 174 |
+
"Content-Type": "application/json",
|
| 175 |
+
"Authorization": f"Bearer {API_KEY}"
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
try:
|
| 179 |
+
response = requests.post(
|
| 180 |
+
API_URL,
|
| 181 |
+
headers=headers,
|
| 182 |
+
json=payload,
|
| 183 |
+
timeout=REQUEST_TIMEOUT
|
| 184 |
+
)
|
| 185 |
+
response.raise_for_status()
|
| 186 |
+
|
| 187 |
+
result = response.json()
|
| 188 |
+
content = result['choices'][0]['message']['content']
|
| 189 |
+
return content
|
| 190 |
+
|
| 191 |
+
except requests.exceptions.RequestException as e:
|
| 192 |
+
print(f"[API ERROR] {e}")
|
| 193 |
+
return None
|
| 194 |
+
except (KeyError, IndexError) as e:
|
| 195 |
+
print(f"[PARSE ERROR] Failed to parse API response: {e}")
|
| 196 |
+
return None
|
| 197 |
+
|
| 198 |
+
return None
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def process_image(image_path: Path, json_path: Path) -> tuple[Optional[str], bool]:
|
| 202 |
+
"""
|
| 203 |
+
Process a single image and return (caption, json_loaded) tuple.
|
| 204 |
+
If JSON missing, uses empty template.
|
| 205 |
+
"""
|
| 206 |
+
# Load JSON metadata
|
| 207 |
+
item, json_loaded = load_json_item(json_path)
|
| 208 |
+
|
| 209 |
+
# Encode image (with resizing if needed)
|
| 210 |
+
try:
|
| 211 |
+
image_data = encode_image_base64(str(image_path), MAX_PIXELS)
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"[ERROR] Failed to encode image {image_path.name}: {e}")
|
| 214 |
+
return None, json_loaded
|
| 215 |
+
|
| 216 |
+
# Prepare messages
|
| 217 |
+
messages = prepare_messages(item, image_data)
|
| 218 |
+
|
| 219 |
+
# Call API (no retries)
|
| 220 |
+
caption = call_caption_api(messages)
|
| 221 |
+
return caption, json_loaded
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def get_base_name(filename: str) -> str:
|
| 225 |
+
"""Get base name without extension."""
|
| 226 |
+
return Path(filename).stem
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def main():
|
| 230 |
+
"""Main processing loop with progress bar."""
|
| 231 |
+
input_dir = Path(INPUT_FOLDER)
|
| 232 |
+
output_dir = Path(OUTPUT_FOLDER)
|
| 233 |
+
|
| 234 |
+
if not input_dir.exists():
|
| 235 |
+
print(f"Error: Input folder '{INPUT_FOLDER}' not found")
|
| 236 |
+
return
|
| 237 |
+
|
| 238 |
+
output_dir.mkdir(exist_ok=True)
|
| 239 |
+
|
| 240 |
+
# Find all image files
|
| 241 |
+
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.webp', '*.bmp']
|
| 242 |
+
image_files = []
|
| 243 |
+
for ext_pattern in image_extensions:
|
| 244 |
+
image_files.extend(input_dir.glob(ext_pattern))
|
| 245 |
+
|
| 246 |
+
# Remove duplicates and sort
|
| 247 |
+
image_files = sorted(set(image_files))
|
| 248 |
+
|
| 249 |
+
if not image_files:
|
| 250 |
+
print(f"No image files found in '{INPUT_FOLDER}'")
|
| 251 |
+
return
|
| 252 |
+
|
| 253 |
+
print(f"Found {len(image_files)} images to process")
|
| 254 |
+
print(f"Configuration:")
|
| 255 |
+
print(f" C_TYPE: {C_TYPE}")
|
| 256 |
+
print(f" USE_NAMES: {USE_NAMES}")
|
| 257 |
+
print(f" ADD_TAGS: {ADD_TAGS}")
|
| 258 |
+
print(f" ADD_CHAR_LIST: {ADD_CHAR_LIST}")
|
| 259 |
+
print(f" ADD_CHARS_TAGS: {ADD_CHARS_TAGS}")
|
| 260 |
+
print(f" ADD_CHARS_DESCR: {ADD_CHARS_DESCR}")
|
| 261 |
+
print(f" MODEL: {MODEL}")
|
| 262 |
+
print(f" API_URL: {API_URL}")
|
| 263 |
+
print(f" NUM_WORKERS: {NUM_WORKERS}")
|
| 264 |
+
print(f" MAX_PIXELS: {MAX_PIXELS} MP")
|
| 265 |
+
print("-" * 50)
|
| 266 |
+
|
| 267 |
+
processed = 0
|
| 268 |
+
failed = 0
|
| 269 |
+
json_missing = 0
|
| 270 |
+
|
| 271 |
+
# Prepare tasks
|
| 272 |
+
tasks = []
|
| 273 |
+
for image_file in image_files:
|
| 274 |
+
base_name = get_base_name(image_file.name)
|
| 275 |
+
json_path = find_json_path(base_name, input_dir)
|
| 276 |
+
tasks.append((image_file, json_path))
|
| 277 |
+
|
| 278 |
+
# Process with thread pool and progress bar
|
| 279 |
+
with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
|
| 280 |
+
future_to_file = {
|
| 281 |
+
executor.submit(process_image, img_path, json_path): (img_path, json_path)
|
| 282 |
+
for img_path, json_path in tasks
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
for future in tqdm(as_completed(future_to_file), total=len(tasks), desc="Processing", unit="img"):
|
| 286 |
+
image_path, json_path = future_to_file[future]
|
| 287 |
+
output_file = output_dir / f"{get_base_name(image_path.name)}{SUFFIX}"
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
caption, json_loaded = future.result()
|
| 291 |
+
if not json_loaded:
|
| 292 |
+
json_missing += 1
|
| 293 |
+
|
| 294 |
+
if caption:
|
| 295 |
+
# Save caption
|
| 296 |
+
try:
|
| 297 |
+
with open(output_file, "w", encoding="utf-8") as f:
|
| 298 |
+
f.write(caption)
|
| 299 |
+
processed += 1
|
| 300 |
+
except Exception as e:
|
| 301 |
+
tqdm.write(f"[ERROR] Failed to save {output_file.name}: {e}")
|
| 302 |
+
failed += 1
|
| 303 |
+
else:
|
| 304 |
+
tqdm.write(f"[ERROR] Captioning failed for {image_path.name}")
|
| 305 |
+
failed += 1
|
| 306 |
+
except Exception as e:
|
| 307 |
+
tqdm.write(f"[ERROR] Task failed for {image_path.name}: {e}")
|
| 308 |
+
failed += 1
|
| 309 |
+
|
| 310 |
+
print("=" * 50)
|
| 311 |
+
print(f"Processing complete:")
|
| 312 |
+
print(f" Processed: {processed}")
|
| 313 |
+
print(f" JSON missing (warnings): {json_missing}")
|
| 314 |
+
print(f" Failed: {failed}")
|
| 315 |
+
print(f" Output folder: {OUTPUT_FOLDER}")
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
if __name__ == "__main__":
|
| 319 |
+
main()
|
scripts/gradio_interface.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import base64
|
| 3 |
+
import io
|
| 4 |
+
import requests
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Dict, Any, Optional, Tuple
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
from PIL import Image
|
| 10 |
+
|
| 11 |
+
# Import prompt building functions from prompts.py
|
| 12 |
+
from prompts import make_user_query, system_prompt, prompts_b
|
| 13 |
+
|
| 14 |
+
# ==================== CONFIGURATION ====================
|
| 15 |
+
|
| 16 |
+
# API settings
|
| 17 |
+
API_URL = "http://127.0.0.1:8000/v1/chat/completions"
|
| 18 |
+
API_KEY = "not-needed"
|
| 19 |
+
|
| 20 |
+
# Image settings
|
| 21 |
+
MAX_PIXELS = 1.0 # Maximum resolution in megapixels (e.g., 4.0 = 4MP)
|
| 22 |
+
|
| 23 |
+
# Request settings
|
| 24 |
+
MAX_TOKENS = 4096
|
| 25 |
+
TEMPERATURE = 0.5
|
| 26 |
+
REQUEST_TIMEOUT = 5 # Reduced for connection check
|
| 27 |
+
WORK_TIMEOUT = 300
|
| 28 |
+
|
| 29 |
+
# Captioning type options (from prompts_b in prompts.py)
|
| 30 |
+
CAPTION_TYPES = list(prompts_b.keys())
|
| 31 |
+
DEFAULT_C_TYPE = CAPTION_TYPES[0] if CAPTION_TYPES else None
|
| 32 |
+
|
| 33 |
+
if not DEFAULT_C_TYPE:
|
| 34 |
+
raise RuntimeError("No caption types available in prompts_b!")
|
| 35 |
+
|
| 36 |
+
# ==================== END CONFIGURATION ====================
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def check_api_connection(api_url: str) -> Tuple[str, str]:
|
| 40 |
+
"""
|
| 41 |
+
Check API connection and return model info.
|
| 42 |
+
Returns (status_message, model_name).
|
| 43 |
+
"""
|
| 44 |
+
try:
|
| 45 |
+
# Try to get models endpoint
|
| 46 |
+
base_url = api_url.rstrip('/').split('/v1/')[0]
|
| 47 |
+
models_url = f"{base_url}/v1/models"
|
| 48 |
+
|
| 49 |
+
response = requests.get(models_url, timeout=REQUEST_TIMEOUT)
|
| 50 |
+
response.raise_for_status()
|
| 51 |
+
|
| 52 |
+
result = response.json()
|
| 53 |
+
if result and 'data' in result and len(result['data']) > 0:
|
| 54 |
+
model_name = result['data'][0].get('id', 'Unknown')
|
| 55 |
+
return "✅ Connected", model_name
|
| 56 |
+
else:
|
| 57 |
+
return "⚠️ Connected (no model info)", "Unknown"
|
| 58 |
+
|
| 59 |
+
except requests.exceptions.ConnectionError:
|
| 60 |
+
return "❌ Connection failed", "N/A"
|
| 61 |
+
except requests.exceptions.Timeout:
|
| 62 |
+
return "❌ Timeout", "N/A"
|
| 63 |
+
except Exception as e:
|
| 64 |
+
return f"❌ Error: {str(e)[:50]}", "N/A"
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def encode_image_base64(image: Image.Image, max_pixels: float = MAX_PIXELS) -> str:
|
| 68 |
+
"""Encode image to base64 string, resizing if necessary."""
|
| 69 |
+
img = image
|
| 70 |
+
|
| 71 |
+
if img.mode != 'RGB':
|
| 72 |
+
img = img.convert('RGB')
|
| 73 |
+
|
| 74 |
+
# Check if resizing needed
|
| 75 |
+
current_pixels = img.width * img.height
|
| 76 |
+
max_pixels_count = max_pixels * 1_000_000
|
| 77 |
+
|
| 78 |
+
if current_pixels >= max_pixels_count:
|
| 79 |
+
# Calculate new dimensions while preserving aspect ratio
|
| 80 |
+
scale = (max_pixels_count / current_pixels) ** 0.5
|
| 81 |
+
new_width = int(img.width * scale)
|
| 82 |
+
new_height = int(img.height * scale)
|
| 83 |
+
|
| 84 |
+
# Resize with high quality
|
| 85 |
+
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
| 86 |
+
# No resize needed
|
| 87 |
+
|
| 88 |
+
# Encode resized image to base64
|
| 89 |
+
buffer = io.BytesIO()
|
| 90 |
+
img.save(buffer, format='JPEG', quality=100)
|
| 91 |
+
return base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def call_caption_api(messages: list, api_url: str = API_URL, model_name: str = "toriigate-0.5") -> Optional[str]:
|
| 95 |
+
"""Call the captioning API."""
|
| 96 |
+
payload = {
|
| 97 |
+
"model": model_name,
|
| 98 |
+
"messages": messages,
|
| 99 |
+
"max_tokens": MAX_TOKENS,
|
| 100 |
+
"temperature": TEMPERATURE,
|
| 101 |
+
"stream": False
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
headers = {
|
| 105 |
+
"Content-Type": "application/json",
|
| 106 |
+
"Authorization": f"Bearer {API_KEY}"
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
try:
|
| 110 |
+
response = requests.post(
|
| 111 |
+
api_url,
|
| 112 |
+
headers=headers,
|
| 113 |
+
json=payload,
|
| 114 |
+
timeout=WORK_TIMEOUT
|
| 115 |
+
)
|
| 116 |
+
response.raise_for_status()
|
| 117 |
+
|
| 118 |
+
result = response.json()
|
| 119 |
+
content = result['choices'][0]['message']['content']
|
| 120 |
+
return content
|
| 121 |
+
|
| 122 |
+
except requests.exceptions.RequestException as e:
|
| 123 |
+
return f"API Error: {e}"
|
| 124 |
+
except (KeyError, IndexError) as e:
|
| 125 |
+
return f"Parse Error: {e}"
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def empty_template() -> Dict[str, Any]:
|
| 129 |
+
"""Return empty template for missing JSON data."""
|
| 130 |
+
return {
|
| 131 |
+
"tags": [],
|
| 132 |
+
"characters": [],
|
| 133 |
+
"char_p_tags": {"chars": {}, "skins": {}},
|
| 134 |
+
"char_descr": {"chars": {}, "skins": {}}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def generate_caption(
|
| 139 |
+
image: Image.Image,
|
| 140 |
+
api_url: str,
|
| 141 |
+
model_name: str,
|
| 142 |
+
c_type: str,
|
| 143 |
+
use_names: bool,
|
| 144 |
+
add_tags: bool,
|
| 145 |
+
add_char_list: bool,
|
| 146 |
+
add_chars_tags: bool,
|
| 147 |
+
add_chars_descr: bool,
|
| 148 |
+
tags_text: str,
|
| 149 |
+
characters_text: str,
|
| 150 |
+
char1_name: str,
|
| 151 |
+
char1_tags: str,
|
| 152 |
+
char2_name: str,
|
| 153 |
+
char2_tags: str,
|
| 154 |
+
char3_name: str,
|
| 155 |
+
char3_tags: str,
|
| 156 |
+
char4_name: str,
|
| 157 |
+
char4_tags: str,
|
| 158 |
+
char5_name: str,
|
| 159 |
+
char5_tags: str,
|
| 160 |
+
char_descr1_name: str,
|
| 161 |
+
char_descr1_text: str,
|
| 162 |
+
char_descr2_name: str,
|
| 163 |
+
char_descr2_text: str,
|
| 164 |
+
char_descr3_name: str,
|
| 165 |
+
char_descr3_text: str,
|
| 166 |
+
char_descr4_name: str,
|
| 167 |
+
char_descr4_text: str,
|
| 168 |
+
char_descr5_name: str,
|
| 169 |
+
char_descr5_text: str
|
| 170 |
+
) -> str:
|
| 171 |
+
"""Generate caption for a single image."""
|
| 172 |
+
if image is None:
|
| 173 |
+
return "Please upload an image first."
|
| 174 |
+
|
| 175 |
+
# Build item dict from inputs
|
| 176 |
+
item = empty_template()
|
| 177 |
+
|
| 178 |
+
# Parse tags
|
| 179 |
+
if add_tags and tags_text.strip():
|
| 180 |
+
item["tags"] = [t.strip() for t in tags_text.split(',') if t.strip()]
|
| 181 |
+
|
| 182 |
+
# Parse characters
|
| 183 |
+
if add_char_list:
|
| 184 |
+
item["characters"] = [c.strip() for c in characters_text.split(',') if c.strip()]
|
| 185 |
+
|
| 186 |
+
# Auto-populate characters list from char tags/descriptions if not manually specified
|
| 187 |
+
if add_chars_tags or add_chars_descr:
|
| 188 |
+
auto_chars = []
|
| 189 |
+
|
| 190 |
+
if add_chars_tags:
|
| 191 |
+
char_entries = [
|
| 192 |
+
char1_name, char2_name, char3_name, char4_name, char5_name
|
| 193 |
+
]
|
| 194 |
+
for name in char_entries:
|
| 195 |
+
if name and name.strip():
|
| 196 |
+
auto_chars.append(name.strip())
|
| 197 |
+
|
| 198 |
+
if add_chars_descr:
|
| 199 |
+
descr_entries = [
|
| 200 |
+
char_descr1_name, char_descr2_name, char_descr3_name,
|
| 201 |
+
char_descr4_name, char_descr5_name
|
| 202 |
+
]
|
| 203 |
+
for name in descr_entries:
|
| 204 |
+
if name and name.strip() and name.strip() not in auto_chars:
|
| 205 |
+
auto_chars.append(name.strip())
|
| 206 |
+
|
| 207 |
+
# Only auto-populate if characters list is empty or not manually set
|
| 208 |
+
if auto_chars and (not add_char_list or not item["characters"]):
|
| 209 |
+
item["characters"] = auto_chars
|
| 210 |
+
add_char_list = True
|
| 211 |
+
|
| 212 |
+
# Parse character tags from structured inputs
|
| 213 |
+
if add_chars_tags:
|
| 214 |
+
chars_dict = {}
|
| 215 |
+
char_entries = [
|
| 216 |
+
(char1_name, char1_tags),
|
| 217 |
+
(char2_name, char2_tags),
|
| 218 |
+
(char3_name, char3_tags),
|
| 219 |
+
(char4_name, char4_tags),
|
| 220 |
+
(char5_name, char5_tags)
|
| 221 |
+
]
|
| 222 |
+
for name, tags_str in char_entries:
|
| 223 |
+
if name is None:
|
| 224 |
+
continue
|
| 225 |
+
name = name.strip()
|
| 226 |
+
if name:
|
| 227 |
+
tags_list = [t.strip() for t in tags_str.split(',') if t.strip()] if tags_str and tags_str.strip() else []
|
| 228 |
+
chars_dict[name] = tags_list
|
| 229 |
+
|
| 230 |
+
if chars_dict:
|
| 231 |
+
item["char_p_tags"] = {"chars": chars_dict, "skins": {}}
|
| 232 |
+
|
| 233 |
+
# Parse character descriptions from structured inputs
|
| 234 |
+
if add_chars_descr:
|
| 235 |
+
descr_dict = {}
|
| 236 |
+
descr_entries = [
|
| 237 |
+
(char_descr1_name, char_descr1_text),
|
| 238 |
+
(char_descr2_name, char_descr2_text),
|
| 239 |
+
(char_descr3_name, char_descr3_text),
|
| 240 |
+
(char_descr4_name, char_descr4_text),
|
| 241 |
+
(char_descr5_name, char_descr5_text)
|
| 242 |
+
]
|
| 243 |
+
for name, descr in descr_entries:
|
| 244 |
+
if name is None or descr is None:
|
| 245 |
+
continue
|
| 246 |
+
name = name.strip()
|
| 247 |
+
descr = descr.strip()
|
| 248 |
+
if name and descr:
|
| 249 |
+
descr_dict[name] = descr
|
| 250 |
+
|
| 251 |
+
if descr_dict:
|
| 252 |
+
item["char_descr"] = {"chars": descr_dict, "skins": {}}
|
| 253 |
+
|
| 254 |
+
# Encode image
|
| 255 |
+
image_data = encode_image_base64(image)
|
| 256 |
+
|
| 257 |
+
# Prepare messages
|
| 258 |
+
user_query = make_user_query(
|
| 259 |
+
item,
|
| 260 |
+
c_type=c_type,
|
| 261 |
+
use_names=use_names,
|
| 262 |
+
add_tags=add_tags,
|
| 263 |
+
add_characters=add_char_list,
|
| 264 |
+
add_char_tags=add_chars_tags,
|
| 265 |
+
add_description=add_chars_descr,
|
| 266 |
+
underscores_replace=False
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
messages = [
|
| 270 |
+
{
|
| 271 |
+
"role": "system",
|
| 272 |
+
"content": [{"type": "text", "text": system_prompt}]
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
"role": "user",
|
| 276 |
+
"content": [
|
| 277 |
+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}},
|
| 278 |
+
{"type": "text", "text": user_query}
|
| 279 |
+
]
|
| 280 |
+
}
|
| 281 |
+
]
|
| 282 |
+
|
| 283 |
+
# Call API
|
| 284 |
+
return call_caption_api(messages, api_url, model_name)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def create_ui():
|
| 288 |
+
"""Create and return the Gradio interface."""
|
| 289 |
+
|
| 290 |
+
with gr.Blocks(title="ToriiGate Captioner", theme=gr.themes.Soft()) as app:
|
| 291 |
+
gr.Markdown("# 🖼️ ToriiGate Captioner")
|
| 292 |
+
|
| 293 |
+
# API URL row with status
|
| 294 |
+
with gr.Row():
|
| 295 |
+
api_url_input = gr.Textbox(
|
| 296 |
+
label="API URL",
|
| 297 |
+
value=API_URL,
|
| 298 |
+
interactive=True,
|
| 299 |
+
scale=4
|
| 300 |
+
)
|
| 301 |
+
api_status = gr.Textbox(
|
| 302 |
+
label="Status",
|
| 303 |
+
value="⏳ Waiting for input...",
|
| 304 |
+
interactive=False,
|
| 305 |
+
scale=1
|
| 306 |
+
)
|
| 307 |
+
model_name_display = gr.Textbox(
|
| 308 |
+
label="Model",
|
| 309 |
+
value="N/A",
|
| 310 |
+
interactive=False,
|
| 311 |
+
scale=1
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
with gr.Row():
|
| 315 |
+
# Left column - Image input
|
| 316 |
+
with gr.Column(scale=1):
|
| 317 |
+
image_input = gr.Image(
|
| 318 |
+
label="Upload Image",
|
| 319 |
+
type="pil",
|
| 320 |
+
height=400
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
gr.Markdown("### Configuration")
|
| 324 |
+
|
| 325 |
+
# Caption type selector
|
| 326 |
+
c_type = gr.Dropdown(
|
| 327 |
+
choices=CAPTION_TYPES,
|
| 328 |
+
value=DEFAULT_C_TYPE,
|
| 329 |
+
label="Caption Type",
|
| 330 |
+
interactive=True
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
# Boolean options with conditional text inputs
|
| 334 |
+
with gr.Group():
|
| 335 |
+
use_names = gr.Checkbox(
|
| 336 |
+
value=True,
|
| 337 |
+
label="Use Names (enable character names)"
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
add_tags = gr.Checkbox(
|
| 341 |
+
value=False,
|
| 342 |
+
label="Add Tags"
|
| 343 |
+
)
|
| 344 |
+
tags_text = gr.Textbox(
|
| 345 |
+
label="Tags (comma-separated)",
|
| 346 |
+
placeholder="e.g., 1girl, blue_hair, school_uniform",
|
| 347 |
+
interactive=False
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
add_char_list = gr.Checkbox(
|
| 351 |
+
value=False,
|
| 352 |
+
label="Add Character List"
|
| 353 |
+
)
|
| 354 |
+
characters_text = gr.Textbox(
|
| 355 |
+
label="Character Names (comma-separated)",
|
| 356 |
+
placeholder="e.g., nishizono_mio, hoshimi_miyabi",
|
| 357 |
+
interactive=False
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
add_chars_tags = gr.Checkbox(
|
| 361 |
+
value=False,
|
| 362 |
+
label="Add Character Tags"
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
with gr.Group(visible=False) as char_tags_group:
|
| 366 |
+
gr.Markdown("**Add character names and their tags**")
|
| 367 |
+
|
| 368 |
+
with gr.Accordion("Character 1", open=True):
|
| 369 |
+
char1_name = gr.Textbox(
|
| 370 |
+
label="Name",
|
| 371 |
+
placeholder="e.g., albedo",
|
| 372 |
+
interactive=True
|
| 373 |
+
)
|
| 374 |
+
char1_tags = gr.Textbox(
|
| 375 |
+
label="Tags (comma-separated)",
|
| 376 |
+
placeholder="e.g., white_hair, green_eyes, horns",
|
| 377 |
+
interactive=True
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
with gr.Accordion("Character 2", open=False):
|
| 381 |
+
char2_name = gr.Textbox(
|
| 382 |
+
label="Name",
|
| 383 |
+
placeholder="e.g., hoshimi_miyabi",
|
| 384 |
+
interactive=True
|
| 385 |
+
)
|
| 386 |
+
char2_tags = gr.Textbox(
|
| 387 |
+
label="Tags (comma-separated)",
|
| 388 |
+
placeholder="e.g., blue_hair, fox_ears",
|
| 389 |
+
interactive=True
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
with gr.Accordion("Character 3", open=False):
|
| 393 |
+
char3_name = gr.Textbox(
|
| 394 |
+
label="Name",
|
| 395 |
+
placeholder="e.g., nishizono_mio",
|
| 396 |
+
interactive=True
|
| 397 |
+
)
|
| 398 |
+
char3_tags = gr.Textbox(
|
| 399 |
+
label="Tags (comma-separated)",
|
| 400 |
+
placeholder="e.g., brown_hair, glasses",
|
| 401 |
+
interactive=True
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
with gr.Accordion("Character 4", open=False):
|
| 405 |
+
char4_name = gr.Textbox(
|
| 406 |
+
label="Name",
|
| 407 |
+
placeholder="e.g.",
|
| 408 |
+
interactive=True
|
| 409 |
+
)
|
| 410 |
+
char4_tags = gr.Textbox(
|
| 411 |
+
label="Tags (comma-separated)",
|
| 412 |
+
placeholder="e.g.",
|
| 413 |
+
interactive=True
|
| 414 |
+
)
|
| 415 |
+
|
| 416 |
+
with gr.Accordion("Character 5", open=False):
|
| 417 |
+
char5_name = gr.Textbox(
|
| 418 |
+
label="Name",
|
| 419 |
+
placeholder="e.g.",
|
| 420 |
+
interactive=True
|
| 421 |
+
)
|
| 422 |
+
char5_tags = gr.Textbox(
|
| 423 |
+
label="Tags (comma-separated)",
|
| 424 |
+
placeholder="e.g.",
|
| 425 |
+
interactive=True
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
char_tags_clear_btn = gr.Button(
|
| 429 |
+
"🗑️ Clear All",
|
| 430 |
+
variant="secondary",
|
| 431 |
+
size="sm"
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
add_chars_descr = gr.Checkbox(
|
| 435 |
+
value=False,
|
| 436 |
+
label="Add Character Descriptions"
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
with gr.Group(visible=False) as char_descr_group:
|
| 440 |
+
gr.Markdown("**Add character descriptions**")
|
| 441 |
+
|
| 442 |
+
with gr.Accordion("Character 1", open=True):
|
| 443 |
+
char_descr1_name = gr.Textbox(
|
| 444 |
+
label="Name",
|
| 445 |
+
placeholder="e.g., albedo",
|
| 446 |
+
interactive=True
|
| 447 |
+
)
|
| 448 |
+
char_descr1_text = gr.Textbox(
|
| 449 |
+
label="Description",
|
| 450 |
+
placeholder="e.g., Albedo is a curvy woman with...",
|
| 451 |
+
lines=3,
|
| 452 |
+
interactive=True
|
| 453 |
+
)
|
| 454 |
+
|
| 455 |
+
with gr.Accordion("Character 2", open=False):
|
| 456 |
+
char_descr2_name = gr.Textbox(
|
| 457 |
+
label="Name",
|
| 458 |
+
placeholder="e.g., hoshimi_miyabi",
|
| 459 |
+
interactive=True
|
| 460 |
+
)
|
| 461 |
+
char_descr2_text = gr.Textbox(
|
| 462 |
+
label="Description",
|
| 463 |
+
placeholder="e.g., Miyabi is a calm and collected...",
|
| 464 |
+
lines=3,
|
| 465 |
+
interactive=True
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
with gr.Accordion("Character 3", open=False):
|
| 469 |
+
char_descr3_name = gr.Textbox(
|
| 470 |
+
label="Name",
|
| 471 |
+
placeholder="e.g., nishizono_mio",
|
| 472 |
+
interactive=True
|
| 473 |
+
)
|
| 474 |
+
char_descr3_text = gr.Textbox(
|
| 475 |
+
label="Description",
|
| 476 |
+
placeholder="e.g., Mio is a cheerful girl with...",
|
| 477 |
+
lines=3,
|
| 478 |
+
interactive=True
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
with gr.Accordion("Character 4", open=False):
|
| 482 |
+
char_descr4_name = gr.Textbox(
|
| 483 |
+
label="Name",
|
| 484 |
+
placeholder="e.g.",
|
| 485 |
+
interactive=True
|
| 486 |
+
)
|
| 487 |
+
char_descr4_text = gr.Textbox(
|
| 488 |
+
label="Description",
|
| 489 |
+
placeholder="e.g.",
|
| 490 |
+
lines=3,
|
| 491 |
+
interactive=True
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
with gr.Accordion("Character 5", open=False):
|
| 495 |
+
char_descr5_name = gr.Textbox(
|
| 496 |
+
label="Name",
|
| 497 |
+
placeholder="e.g.",
|
| 498 |
+
interactive=True
|
| 499 |
+
)
|
| 500 |
+
char_descr5_text = gr.Textbox(
|
| 501 |
+
label="Description",
|
| 502 |
+
placeholder="e.g.",
|
| 503 |
+
lines=3,
|
| 504 |
+
interactive=True
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
char_descr_clear_btn = gr.Button(
|
| 508 |
+
"🗑️ Clear All",
|
| 509 |
+
variant="secondary",
|
| 510 |
+
size="sm"
|
| 511 |
+
)
|
| 512 |
+
|
| 513 |
+
generate_btn = gr.Button("🚀 Generate Caption", variant="primary", size="lg")
|
| 514 |
+
|
| 515 |
+
# Right column - Output
|
| 516 |
+
with gr.Column(scale=1):
|
| 517 |
+
output_text = gr.Textbox(
|
| 518 |
+
label="Caption Output",
|
| 519 |
+
lines=20,
|
| 520 |
+
max_lines=50,
|
| 521 |
+
interactive=False
|
| 522 |
+
)
|
| 523 |
+
|
| 524 |
+
# Toggle text inputs based on checkbox state
|
| 525 |
+
def toggle_input(is_checked: bool, input_component):
|
| 526 |
+
return gr.update(interactive=is_checked)
|
| 527 |
+
|
| 528 |
+
add_tags.change(
|
| 529 |
+
lambda x: toggle_input(x, tags_text),
|
| 530 |
+
inputs=add_tags,
|
| 531 |
+
outputs=tags_text
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
add_char_list.change(
|
| 535 |
+
lambda x: toggle_input(x, characters_text),
|
| 536 |
+
inputs=add_char_list,
|
| 537 |
+
outputs=characters_text
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
add_chars_tags.change(
|
| 541 |
+
fn=lambda x: gr.update(visible=x),
|
| 542 |
+
inputs=add_chars_tags,
|
| 543 |
+
outputs=char_tags_group
|
| 544 |
+
)
|
| 545 |
+
|
| 546 |
+
add_chars_descr.change(
|
| 547 |
+
fn=lambda x: gr.update(visible=x),
|
| 548 |
+
inputs=add_chars_descr,
|
| 549 |
+
outputs=char_descr_group
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
# API URL change handler
|
| 553 |
+
api_url_input.change(
|
| 554 |
+
fn=check_api_connection,
|
| 555 |
+
inputs=api_url_input,
|
| 556 |
+
outputs=[api_status, model_name_display]
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
# Wire up generate button
|
| 560 |
+
generate_btn.click(
|
| 561 |
+
fn=generate_caption,
|
| 562 |
+
inputs=[
|
| 563 |
+
image_input,
|
| 564 |
+
api_url_input,
|
| 565 |
+
model_name_display,
|
| 566 |
+
c_type,
|
| 567 |
+
use_names,
|
| 568 |
+
add_tags,
|
| 569 |
+
add_char_list,
|
| 570 |
+
add_chars_tags,
|
| 571 |
+
add_chars_descr,
|
| 572 |
+
tags_text,
|
| 573 |
+
characters_text,
|
| 574 |
+
char1_name,
|
| 575 |
+
char1_tags,
|
| 576 |
+
char2_name,
|
| 577 |
+
char2_tags,
|
| 578 |
+
char3_name,
|
| 579 |
+
char3_tags,
|
| 580 |
+
char4_name,
|
| 581 |
+
char4_tags,
|
| 582 |
+
char5_name,
|
| 583 |
+
char5_tags,
|
| 584 |
+
char_descr1_name,
|
| 585 |
+
char_descr1_text,
|
| 586 |
+
char_descr2_name,
|
| 587 |
+
char_descr2_text,
|
| 588 |
+
char_descr3_name,
|
| 589 |
+
char_descr3_text,
|
| 590 |
+
char_descr4_name,
|
| 591 |
+
char_descr4_text,
|
| 592 |
+
char_descr5_name,
|
| 593 |
+
char_descr5_text
|
| 594 |
+
],
|
| 595 |
+
outputs=output_text
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
# Clear character tags button handler
|
| 599 |
+
def clear_char_tags():
|
| 600 |
+
return "", "", "", "", "", "", "", "", "", ""
|
| 601 |
+
|
| 602 |
+
char_tags_clear_btn.click(
|
| 603 |
+
fn=clear_char_tags,
|
| 604 |
+
inputs=[],
|
| 605 |
+
outputs=[
|
| 606 |
+
char1_name, char1_tags,
|
| 607 |
+
char2_name, char2_tags,
|
| 608 |
+
char3_name, char3_tags,
|
| 609 |
+
char4_name, char4_tags,
|
| 610 |
+
char5_name, char5_tags
|
| 611 |
+
]
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
# Clear character descriptions button handler
|
| 615 |
+
def clear_char_descr():
|
| 616 |
+
return "", "", "", "", "", "", "", "", "", ""
|
| 617 |
+
|
| 618 |
+
char_descr_clear_btn.click(
|
| 619 |
+
fn=clear_char_descr,
|
| 620 |
+
inputs=[],
|
| 621 |
+
outputs=[
|
| 622 |
+
char_descr1_name, char_descr1_text,
|
| 623 |
+
char_descr2_name, char_descr2_text,
|
| 624 |
+
char_descr3_name, char_descr3_text,
|
| 625 |
+
char_descr4_name, char_descr4_text,
|
| 626 |
+
char_descr5_name, char_descr5_text
|
| 627 |
+
]
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
return app
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
if __name__ == "__main__":
|
| 634 |
+
app = create_ui()
|
| 635 |
+
app.launch(server_name="127.0.0.1", server_port=7860)
|
scripts/prompts.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
prompts_b = {
|
| 4 |
+
"long_thoughts_v2": """Your answer must contain 6 parts:
|
| 5 |
+
<format>
|
| 6 |
+
# 1. Thoughts about characters
|
| 7 |
+
You need to think here and compare peoples/creatures that you see on the picture with given popular tags, or descriptions, or your memories for each characters to determine who is who.
|
| 8 |
+
# 2. Key details
|
| 9 |
+
Here you need to determine key details on comic and list them.
|
| 10 |
+
# 3. Long description
|
| 11 |
+
Here come up with a long and detailed description of image content. Be creative, mention all detailes you listed above and other important things.
|
| 12 |
+
# 4. Detailed description for each character
|
| 13 |
+
## Name 1
|
| 14 |
+
Detailed and long description for the first character
|
| 15 |
+
## Name 2
|
| 16 |
+
Same for each one (if present)
|
| 17 |
+
</format>
|
| 18 |
+
""",
|
| 19 |
+
"long_thoughts": """Your answer must contain 6 parts:
|
| 20 |
+
<format>
|
| 21 |
+
# 1. Thoughts about characters
|
| 22 |
+
You need to think here and compare peoples/creatures that you see on the picture with given popular tags, or descriptions, or your memories for each characters to determine who is who.
|
| 23 |
+
If no characters are listed in input - just write here "No named characters"
|
| 24 |
+
# 2. General description
|
| 25 |
+
A one-two paragraph summary of the image. Mention all individual parts/objects/characters/positions/interactions/etc.
|
| 26 |
+
# 3. Detailed description for each character
|
| 27 |
+
## Character name 1 (put here the name if any)
|
| 28 |
+
In very detail write about features, poses, look, used objects, interactions, and other things for character on the picture.
|
| 29 |
+
## Character name 2 (put here the name if any)
|
| 30 |
+
Same for each character.
|
| 31 |
+
...
|
| 32 |
+
# 4. Individual Parts
|
| 33 |
+
List the individual things you see in the image and their relative positions to other parts. Use a numbered list of between 5 and 20 items depending on image complexity.
|
| 34 |
+
# 5. Texts on image
|
| 35 |
+
Mention every texts that you notice on image, including types (a speech bubble, watermark, banner, etc.) and content.
|
| 36 |
+
# 6. Background and effects
|
| 37 |
+
Give some info about objects on background, describe the location (if seen). Then mention effects (style, camera angle, clarity/blurrines, effects like depth of field, strange angle/forshortening, etc.)
|
| 38 |
+
</format>
|
| 39 |
+
""",
|
| 40 |
+
"json": """Use json-style caption for given image with following structure:
|
| 41 |
+
{"character" : "Description for character or object. Name (if defined), main details, features, position, pose, etc.",
|
| 42 |
+
/or in case of multiple
|
| 43 |
+
"character_1" : "Description for first"
|
| 44 |
+
"character_2" : "Description for second ",
|
| 45 |
+
"character_N"...
|
| 46 |
+
/or if there are no characters
|
| 47 |
+
"main content" : "long and detailed description of main content of image that might be the main focus if characters are missing",
|
| 48 |
+
/
|
| 49 |
+
"background" : "Detailed descritpion of background and it's content",
|
| 50 |
+
"image_effects" : "If there are some visual effects like fisheye distortion, chromatic aberration, glitches, messy drawing or anything else - write about it. If it's just a general anime art - omit this field."
|
| 51 |
+
"texts" : "Speech bubbles, bars, marks, signs etc. with texts if present, else None",
|
| 52 |
+
"atmosphere" : "...",
|
| 53 |
+
}
|
| 54 |
+
In special cases you can add extra keys.
|
| 55 |
+
""",
|
| 56 |
+
"long": """Make a caption for given image with natural text. Use 2 to 5 paragraphs. Make your description long and vivid, mentioning all the details.
|
| 57 |
+
""",
|
| 58 |
+
"min_structured_md": """Your answer must contain 3 parts:
|
| 59 |
+
<format>
|
| 60 |
+
# 1. Thoughts about characters
|
| 61 |
+
You need to think here and compare peoples/creatures that you see on the picture with given popular tags, or descriptions, or your memories for each characters to determine who is who.
|
| 62 |
+
If no characters are listed in input - just write here "No named characters"
|
| 63 |
+
# 2. Key details
|
| 64 |
+
Here you need to write about the key details on image, prefere using regular text.
|
| 65 |
+
# 3. Structured description
|
| 66 |
+
## General
|
| 67 |
+
Write about general composition, content of image, background and all things that are not related to characters directly.
|
| 68 |
+
## Character name 1 (put here the name if any)
|
| 69 |
+
Write about datails and content related to specific character, including features, poses, look, used objects, interactions, and other things.
|
| 70 |
+
## Character name 2 (put here the name if any)
|
| 71 |
+
Same for each character.
|
| 72 |
+
## Image effects
|
| 73 |
+
Mention image effect, style, camera angle
|
| 74 |
+
</format>
|
| 75 |
+
In general stick to shorter descriptions.
|
| 76 |
+
""",
|
| 77 |
+
"json_comic": """Use json-style caption to describe to comin, stick to following structure:
|
| 78 |
+
{
|
| 79 |
+
"comic_format": "menation the format, for example Comic of N frames",
|
| 80 |
+
"1st_frame": "Main description of the content for fist frame",
|
| 81 |
+
"2nd_frame": "Same for the second",
|
| 82 |
+
...
|
| 83 |
+
"Nth_ftame": "...",
|
| 84 |
+
"character_1": "Describe the characters in comic",
|
| 85 |
+
...
|
| 86 |
+
"character_N": "Separate description for each",
|
| 87 |
+
"meaning": "Try to guess general mood, vibe and meaning of the comic"
|
| 88 |
+
}
|
| 89 |
+
""",
|
| 90 |
+
"md_comic": """Use markdown format to describe to comic, 5 parts are recommended:
|
| 91 |
+
<format>
|
| 92 |
+
# 1. Thoughts about characters
|
| 93 |
+
You need to think here and compare peoples/creatures that you see on the picture with given popular tags, or descriptions, or your memories for each characters to determine who is who.
|
| 94 |
+
# 2. Key details
|
| 95 |
+
Here you need to determine key details on comic and list them.
|
| 96 |
+
# 3. Comic format
|
| 97 |
+
In this section come up with the description of comic format, how many pages there are, horisontal/vertical orientation and other things. Optionally you can list main characters here.
|
| 98 |
+
# 4. Details for each frame
|
| 99 |
+
## 4.1 Frame 1 (position)
|
| 100 |
+
Description for each frame, includding characters, objects, interactions, texts/speech bubbles and other things. Be detailed but not overdoo.
|
| 101 |
+
## 4.2 Frame 2 (position)
|
| 102 |
+
Same for each frame.
|
| 103 |
+
...
|
| 104 |
+
# 5. Extra comment
|
| 105 |
+
Here you should write general desciption and some other info about the image.
|
| 106 |
+
</format>
|
| 107 |
+
""",
|
| 108 |
+
"min_structured_json": """
|
| 109 |
+
Use json-style caption for given image with following structure:
|
| 110 |
+
{"General" : "Here you need to come up with general/common information about picture, overall composition. Stick to shorter phrases and tags instead of long purple prose. Avoid bullets and markdown, write in plain text.",
|
| 111 |
+
"character_1 (put here the name if any)" : "Description of first character."
|
| 112 |
+
"character_2 (if present" : "Description for second ",
|
| 113 |
+
"character_N"
|
| 114 |
+
...
|
| 115 |
+
"image_effects" : "Mention here effects on image if there are any distinct."
|
| 116 |
+
"texts" : "Speech bubbles, bars, marks, signs etc. with texts if present, else None",
|
| 117 |
+
"watermarks" : "If present",
|
| 118 |
+
}
|
| 119 |
+
Prefere shorter description and tags.
|
| 120 |
+
""",
|
| 121 |
+
"chroma-style": """Your task is to describe the picture in very detail using a structure of 4 parts.
|
| 122 |
+
### 1. Regular Summary:
|
| 123 |
+
[A one-paragraph summary of the image. The paragraph should mention all individual parts/things/characters/etc.]
|
| 124 |
+
### 2. Individual Parts:
|
| 125 |
+
[List the individual things you see in the image and their relative positions to other parts. Use a numbered list of between 5 and 30 items depending on image complexity.]
|
| 126 |
+
### 3. Midjourney-Style Summary:
|
| 127 |
+
[A summary that has higher concept density by using comma-separated partial sentences instead of proper sentence structure.]
|
| 128 |
+
### 4. DeviantArt Commission Request
|
| 129 |
+
[Write a description as if you're commissioning this *exact* image via someone who is currently taking requests.]
|
| 130 |
+
""",
|
| 131 |
+
"short":"""The caption for image should be quite short without long purple prose and slop. Cover main objects and details.
|
| 132 |
+
""",
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
prompts_names_only = {
|
| 136 |
+
"long_thoughts_v2":True,
|
| 137 |
+
"long_thoughts": True,
|
| 138 |
+
"json": False,
|
| 139 |
+
"long": False,
|
| 140 |
+
"json_comic": False,
|
| 141 |
+
"md_comic": True,
|
| 142 |
+
"min_structured_md": True,
|
| 143 |
+
"min_structured_json": False,
|
| 144 |
+
"chroma-style": False,
|
| 145 |
+
"short":False,
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def make_user_query(item, c_type, use_names, add_tags, add_characters, add_char_tags, add_description, underscores_replace = False):
|
| 150 |
+
tags = item.get('tags', [])
|
| 151 |
+
random.shuffle(tags)
|
| 152 |
+
if underscores_replace:
|
| 153 |
+
tags = [a.replace('_', ' ') if len(a)>3 else a for a in tags]
|
| 154 |
+
tags_string = ', '.join(tags)
|
| 155 |
+
else:
|
| 156 |
+
tags_string = ' '.join(tags)
|
| 157 |
+
|
| 158 |
+
user_request = '# Captioning format:\n'
|
| 159 |
+
user_request += prompts_b[c_type]
|
| 160 |
+
user_request += '\n'
|
| 161 |
+
|
| 162 |
+
if add_tags:
|
| 163 |
+
user_request += f"# Booru tags for the image\n[{tags_string}]\n\n"
|
| 164 |
+
|
| 165 |
+
if use_names: #Имена персонажей
|
| 166 |
+
if add_characters:
|
| 167 |
+
chars_tags = item.get('characters',[])
|
| 168 |
+
if underscores_replace:
|
| 169 |
+
tags = [a.replace('_', ' ') for a in chars_tags]
|
| 170 |
+
chars_string = ', '.join(chars_tags)
|
| 171 |
+
else:
|
| 172 |
+
chars_string = ' '.join(chars_tags)
|
| 173 |
+
|
| 174 |
+
user_request += f"# Characters on picture:\nHere are names/tags for characters from the picture, make sure to use them: [{chars_string}].\n\n"
|
| 175 |
+
|
| 176 |
+
chars_popular_tags = (item.get('char_p_tags',"{'chars':{},'skins':{}}"))
|
| 177 |
+
chars_description = (item.get('char_descr',"{'chars':{},'skins':{}}"))
|
| 178 |
+
|
| 179 |
+
if len(chars_popular_tags['chars']) > 0 and (add_char_tags or add_description):
|
| 180 |
+
|
| 181 |
+
user_request += "# Known traits for characters\n"
|
| 182 |
+
char_underscores = underscores_replace
|
| 183 |
+
|
| 184 |
+
if add_char_tags:
|
| 185 |
+
user_request += "Here are popular tags for each characters on picture:\n"
|
| 186 |
+
|
| 187 |
+
for c_name, c_tags in chars_popular_tags['chars'].items():
|
| 188 |
+
name = c_name.replace('_',' ') if char_underscores else c_name
|
| 189 |
+
tags_s = (', '.join([a.replace('_', ' ') if len(a)>3 else a for a in c_tags]) if char_underscores else
|
| 190 |
+
' '.join(c_tags))
|
| 191 |
+
user_request += f"{name}: [{tags_s}]\n"
|
| 192 |
+
if len(chars_popular_tags['skins']) > 0:
|
| 193 |
+
user_request += "Extra tags for characters skins:\n"
|
| 194 |
+
for c_name, c_tags in chars_popular_tags['skins'].items():
|
| 195 |
+
name = c_name.replace('_',' ') if char_underscores else c_name
|
| 196 |
+
tags_s = (', '.join([a.replace('_', ' ') if len(a)>3 else a for a in c_tags]) if char_underscores else
|
| 197 |
+
' '.join(c_tags))
|
| 198 |
+
user_request += f"{name}: [{tags_s}]\n"
|
| 199 |
+
|
| 200 |
+
elif add_description:
|
| 201 |
+
|
| 202 |
+
user_request += "Here are general descriptions for each characters on the picture:\n"
|
| 203 |
+
for c_name, c_descr in chars_description['chars'].items():
|
| 204 |
+
name = c_name.replace('_',' ') if char_underscores else c_name
|
| 205 |
+
user_request += f"## {name}\n{c_descr}\n\n"
|
| 206 |
+
if len(chars_description['skins']) > 0:
|
| 207 |
+
user_request += "Here are also descriptions for specific skin of characters:\n"
|
| 208 |
+
for c_name, c_descr in chars_description['skins'].items():
|
| 209 |
+
name = c_name.replace('_',' ') if char_underscores else c_name
|
| 210 |
+
user_request += f"## {name}\n{c_descr}\n\n"
|
| 211 |
+
else:
|
| 212 |
+
user_request += "# Characters on picture:\nTry to recognize the characters in the picture and use their names.\n"
|
| 213 |
+
|
| 214 |
+
user_request += '\n'
|
| 215 |
+
else:
|
| 216 |
+
user_request += "# Characters on picture:\nAvoid to guess names for characters.\n"
|
| 217 |
+
|
| 218 |
+
return user_request
|
| 219 |
+
|
| 220 |
+
system_prompt = "You are image captioning expert. Describe user's picture according to requested format and instructions."
|
scripts/transformers_ver.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from prompts import make_user_query, system_prompt
|
| 2 |
+
|
| 3 |
+
from transformers import (
|
| 4 |
+
Qwen3_5ForConditionalGeneration,
|
| 5 |
+
AutoProcessor,
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
from PIL import Image
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
MODEL_PATH = "M:/ai/qwen3.5_mm_trainer/Qwen3.5-4B-Base_k2"
|
| 12 |
+
|
| 13 |
+
DEVICE = 'cuda'
|
| 14 |
+
model = Qwen3_5ForConditionalGeneration.from_pretrained(
|
| 15 |
+
MODEL_PATH,
|
| 16 |
+
torch_dtype=torch.bfloat16,
|
| 17 |
+
attn_implementation="sdpa",
|
| 18 |
+
device_map=DEVICE
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
processor = AutoProcessor.from_pretrained(
|
| 22 |
+
MODEL_PATH,
|
| 23 |
+
min_pixels=256*32*32,
|
| 24 |
+
padding_side="right"
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
C_TYPE = 'long_thoughts_v2'
|
| 28 |
+
USE_NAMES = True
|
| 29 |
+
ADD_TAGS = False
|
| 30 |
+
ADD_CHAR_LIST = False
|
| 31 |
+
ADD_CHARS_TAGS = False
|
| 32 |
+
ADD_CHARS_DESCR = False
|
| 33 |
+
|
| 34 |
+
def prepare_messages(item):
|
| 35 |
+
user_query = make_user_query(item,
|
| 36 |
+
C_TYPE, USE_NAMES, ADD_TAGS, ADD_CHAR_LIST, ADD_CHARS_TAGS, ADD_CHARS_DESCR
|
| 37 |
+
)
|
| 38 |
+
return [
|
| 39 |
+
{
|
| 40 |
+
"role": "system",
|
| 41 |
+
"content": [{"type": "text", "text": system_prompt}]
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"role": "user",
|
| 45 |
+
"content": [
|
| 46 |
+
{"type": "image"},
|
| 47 |
+
{"type": "text", "text": user_query},
|
| 48 |
+
],
|
| 49 |
+
}
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
img = Image.open('test_image.png')
|
| 53 |
+
images = [img]
|
| 54 |
+
msgs = prepare_messages({})
|
| 55 |
+
texts = [processor.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)]
|
| 56 |
+
inputs = processor(text=texts, images=images, return_tensors="pt")
|
| 57 |
+
inputs = {k:v.to(DEVICE) for k,v in inputs.items()}
|
| 58 |
+
with torch.no_grad():
|
| 59 |
+
generate_ids = model.generate(**inputs, max_new_tokens=1024)
|
| 60 |
+
generated_texts = processor.batch_decode(
|
| 61 |
+
generate_ids[:, inputs["input_ids"].shape[1]:],
|
| 62 |
+
skip_special_tokens=True
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
print(generated_texts[0])
|
| 66 |
+
|
| 67 |
+
|