Outfits, templates and art styles
Who is this for
Section titled “Who is this for”A developer who has to change what guests can choose on the kiosk or how the stickers look: a new outfit card, a new body drawing, a new art style, different poses.
- Everything a guest can choose is one list:
bot/catalog.py→TEMPLATES. List order = card order on the kiosk. - An outfit is a text (
outfit_text) that goes into the image prompt in figure mode, plus a preview card. Adding one needs no new drawing of a body. - A body template is a PNG used only in composite mode, where code pastes an AI-drawn head at
head_anchor. - An art style is a text preset in
ai_pipeline.ART_STYLES, registered inconfig.ART_STYLE_IDS, selected by theART_STYLEvariable. - Generating preview cards and comparing styles are paid operations with hard caps. Re-encoding and re-cropping existing previews are free.
- Several tests pin the catalogue order, the style list and the emoji list on purpose. Update them in the same commit.
| Term | Meaning |
|---|---|
| Figure mode | STICKER_RENDER_MODE=figure. The model draws whole chest-up figures on a green sheet; code cuts, outlines and exports them. Clothes come from outfit_text. |
| Composite mode | STICKER_RENDER_MODE=composite. The model draws only heads; code places each head on a body template PNG. Also the automatic fallback when a figure sheet never becomes valid. |
| Template | One entry of catalog.TEMPLATES. The same entry serves both modes: outfit_text for figure, file + head_anchor for composite. |
| Placeholder | A template with ready=False: a plain silhouette, hidden from guests unless SHOW_PLACEHOLDER_TEMPLATES=1. |
| Sheet | One generated image holding several figures or heads on a chroma-key green background (#00FF00). |
More terms are in the glossary.
The catalogue entry
Section titled “The catalogue entry”@dataclass(frozen=True)class Template: id: str label: str file: str head_anchor: tuple[int, int, int, int] | None = None head_scale: float = 1.0 head_dy: int = 0 head_dx: int = 0 outfit_text: str = OUTFIT_PRESET outfit_ref: str | None = None ready: bool = False preview: str | None = None| Field | Used by | Meaning |
|---|---|---|
id |
everything | Stable identifier stored in sessions.template_id and jobs.template_id. Never rename an id that has jobs. |
label |
kiosk card | Text shown to the guest (Russian today). |
file |
composite, API | Body PNG in TEMPLATES_DIR. Outfits without their own body reuse m1.png. |
head_anchor |
composite | (x, y, w, h) of the head circle in template pixels. None = the generic default head box (placeholders). |
head_scale, head_dy, head_dx |
composite | Per-template tuning: scale of the head box around its bottom centre, then shift in pixels (+ = down / right). |
outfit_text |
figure | English description of the clothes; inserted into the prompt (“outfit lock”) and into the QA prompt. |
outfit_ref |
figure | Optional reference image of the clothes, path relative to TEMPLATES_DIR. Missing file = warning, text only. |
ready |
API | True = shown to guests. False = placeholder. |
preview |
API | Card image file in TEMPLATES_DIR. None = file is served instead. |
Facts worth knowing before you edit:
DEFAULT_TEMPLATE_ID = "m1". A job without a template uses it (log markerTEMPLATE_DEFAULT).- A job whose
template_idis not in the catalogue fails immediately, without retries. Do not remove an entry while jobs that use it are queued. POST /session/{token}/templaterejects unknown ids and hidden placeholders with422.GET /templates/{id}.pngserves only files referenced by the catalogue, never the whole folder.
Add an outfit
Section titled “Add an outfit”An outfit needs: one constant with the text, one Template line, two preview files.
-
Write the outfit text as a module constant in
bot/catalog.py, in English. Follow the rules the existing texts follow:Rule Why Name the colour, cut and fit. The model otherwise varies the garment between the 12 stickers. Describe the collar or neckline and what is worn underneath. These are the parts visible in every frame. Say explicitly what is visible in close-ups (head and shoulders). Half of the stickers are close-ups; the outfit must still be recognisable there. Forbid prints, logos, text, patterns, jewellery, accessories, hats, glasses, headphones. The model copies accessories from the selfie or invents them. If an accessory is part of the outfit (a tie), say that it is part of the outfit and must be drawn on every figure. The general “no accessories” ban would otherwise remove it on some figures. No green tones anywhere. The sheet background is chroma-key green; green clothes are cut out as background. State that the outfit does not change the face, hairstyle or skin tone and adds no make-up; gender and age come from the photo. Outfits are unisex; a “dress” must not turn every guest into a different person. Append the shared constant _OUTFIT_KEEPS_GUEST. -
Add the entry to
TEMPLATESat the position where the card should appear:Template("u6", "Подпись карточки", "m1.png", head_anchor=M1_HEAD_ANCHOR,outfit_text=OUTFIT_NEW, preview="u6_preview.webp", ready=True),file="m1.png"withhead_anchor=M1_HEAD_ANCHORkeeps the composite fallback working: if the figure sheet fails, the guest still gets a pack, wearing the default body. -
Create the preview card. Two files per outfit:
File Format Limit In git templates/preview_src/<id>.webp1024 × 1024 WebP, source ≤ 250 KB yes templates/<id>_preview.webp(thepreviewfield)640 × 640 WebP, quality 80, served to the kiosk ≤ 70 KB; quality steps down by 5, never below 60 yes Raw 2K renders and candidates stay in
out/outfit_previews/, outside git.Generate it with
tools/make_outfit_previews.py:Mode Paid What it does default (no mode flag) yes Generates the cards that do not exist yet, from outfit_textonly — no guest photo.--only u6limits it,--forceredraws existing ones.--candidates Nyes N variants per outfit into out/outfit_previews/<id>_cand<k>.webp; installs the first.--characteryes Draws a waist-up fictional character instead of a headless torso; the source for --neckdown.--pick u6=2no Installs candidate 2 for u6from files already on disk.--neckdownno Re-crops existing raw renders ( out/outfit_previews/<id>_raw.png) from the neck down, same square and background for all. Needs a cut line for the id in the tool’sNECKDOWN_CUTtable.--recompressno Rebuilds every served card from templates/preview_src/<id>.webp. Options--size(640),--quality(80),--max-kb(70).Paid modes: at most 6 images and $0.50 per run, enforced in code;
--price-per-imageis mandatory; the tool prints an estimate and always asks for confirmation; only the Google provider is used. The tool calls the event guard first. Agree the budget before running.Terminal window python tools\make_outfit_previews.py --only u6 --candidates 2 --price-per-image <price>python tools\make_outfit_previews.py --pick u6=2python tools\make_outfit_previews.py --recompressA card must show clothes only — a fictional torso, never a real person.
-
Update the pinned order in tests:
READYintests/test_outfits.pyand the two id lists intests/test_api.py. -
Run the checks from the table at the end of this page.
-
Tell the landing page. Its mock kiosk mirrors the real outfit set and order; see Showcase packs.
Ship the new outfit
Section titled “Ship the new outfit”- Commit the catalogue change, both preview files and the updated tests (
push.ps1deploys only committed files). - Deploy and restart both services:
powershell -ExecutionPolicy Bypass -File deploy\push.ps1 -Server root@<server> -Restart. The catalogue is read at start-up, so both the API (cards) and the bot (prompts) must restart. - Check
https://stickers.eventtalks.uz/templates: the new id is listed in the intended position, and itspreviewURL returns the image. - Open the kiosk, reach the outfit screen and confirm the card.
- Make one live test pack with the new outfit before any event (about $0.42; grant yourself one more pack in the admin panel first). Prompt texts are only judged by eye.
- Tell the landing team if the showcase should show it — see Showcase packs.
Order, placeholders and SHOW_PLACEHOLDER_TEMPLATES
Section titled “Order, placeholders and SHOW_PLACEHOLDER_TEMPLATES”GET /templatesreturnsvisible_templates(...): entries withready=True, in list order. Ready outfits are listed first, placeholders last.SHOW_PLACEHOLDER_TEMPLATES=1also returns entries withready=False. This is for development only.- To hide an outfit without deleting it, set
ready=False. Jobs already queued with that id still work, because the entry stays in the catalogue. - The kiosk caches cards by content hash (
?v=<hash>), so a replaced file is picked up without clearing any cache.
Add a body template for composite mode
Section titled “Add a body template for composite mode”A body template is needed only when composite mode must show a body other than the default one.
-
Draw the body: a PNG on a flat green background (
#00FF00), with a flat magenta circle (#FF00FF) exactly where the head will sit. -
Make sure the id exists. The tool looks up
TEMPLATES_BY_ID[<id>].fileand reads the size of the current file, so the catalogue entry and a file must exist first. The usual route is to take over a placeholder id (m2,m3,f1–f3);python tools\make_placeholder_templates.pyrecreates placeholder files. -
Convert it:
Terminal window python tools\make_template_from_raw.py <raw>.png --id <id>The tool (source): removes the green that is connected to the image border, removes green spill on the contour, makes the part of the circle above the clothes transparent, fills the part hugged by a hood or collar with the inner collar colour (otherwise transparent crescents appear between neck and collar), crops, fits the result into the size of the existing file and rescales the anchor.
It writes
templates/<id>.pngandtemplates/<id>_template.json(anchor and collar line), keeps the previous file once as<id>_placeholder_backup.png/.json, and prints JSON withhead_anchor. -
Copy
head_anchorfrom the output into the catalogue entry. -
Preview on real heads:
Terminal window python tools\preview_template.py --id <id> --sheet <head_sheet>.pngpython tools\preview_template.py --id <id> --sheet <head_sheet>.png --variantsWithout
--sheetthe tool takes the reference sheet of the latest job inbot\data\sessions. It builds the stickers through the same code path as the worker and checks: no magenta or green left, no white fringe on the body, no gap at the neck seam, outline not cropped, each sticker ≤ 512 KB. Exit code 0 = all passed.--variantsadds rows for head scales 1.35 / 1.55 / 1.75 with a matching vertical shift. -
Tune
head_scale,head_dy,head_dxin the catalogue and repeat step 5 until the proportions are right. -
Set
ready=Trueand give the entry alabel, anoutfit_text(so figure mode draws matching clothes) and apreview. -
Never commit
preview_*.png. They contain guest faces and are git-ignored.
Add an art style
Section titled “Add an art style”Styles apply to figure mode. The style of a job is fixed when the job is created (jobs.art_style).
-
Add a preset to
ART_STYLESinbot/services/ai_pipeline.py:Key Content labelShort human-readable name. styleThe rendering style sentence inserted as Style: … .chestHead-to-figure proportion rule for chest-up frames. closeupProportion rule for close-up frames. -
Register the id in
ART_STYLE_IDSinbot/config.py, in the same position as in the dictionary. Start-up validation uses this tuple; anART_STYLEvalue outside it stops the process. -
Update the pinned list in
tests/test_figure_mode.py(test_art_styles_are_separate_blocks): it asserts that the dictionary keys,ART_STYLE_IDSand a literal tuple are identical, and that every style produces a different prompt. -
Select it:
ART_STYLE=<id>inbot/.env, then restart both processes (the API writes the style into the job, the worker renders it). -
Evaluate on a face with
tools/style_compare.py. This is paid: one sheet per style on one photo. Caps enforced in code: at most 8 generations and $2 per run;--price-per-imageis mandatory; the budget is checked before every generation. Limit the styles:Terminal window python tools\style_compare.py --photo <photo>.jpg --styles <new_id>,cute_cartoon --price-per-image <price>Results, including the guest’s face, go to
out/style_compare/and stay outside git. -
Check the landing copy. The page describes the style in words and must not use the internal style id; see Code rules.
Do not change DEFAULT_ART_STYLE casually: prompts built without a style name must stay identical to the
cute_cartoon prompts, and a test asserts it.
Change poses and emoji
Section titled “Change poses and emoji”Figure mode
Section titled “Figure mode”Twelve (pose, emoji) pairs in bot/catalog.py:
-
FIGURE_POSES_SHEET_1— stickers 1–6,FIGURE_POSES_SHEET_2— stickers 7–12. Sticker number = position in the concatenated list. The emoji is what Telegram uses to suggest the sticker while typing. -
FIGURE_FRAMES— frame type by position within a group of six: threechest, then threecloseup. StickernusesFIGURE_FRAMES[(n - 1) % 6]. -
SHEET_LAYOUTS— how the 12 stickers are spread over sheets:Layout Sheets Grid per sheet Aspect 2x62 × 6 figures 3 columns × 2 rows FIGURE_SHEET_ASPECT3x43 × 4 figures 2 × 2 1:1In both layouts every sheet has chest-up frames in the top row and close-ups in the bottom row.
Steps:
- Edit the pose text and emoji in place; keep 6 + 6 entries.
- Put gestures with hands on
chestpositions (1–3 and 7–9). The existing close-up gestures are held beside the cheek. Hands must never cover the face: on close-ups the face is what carries the likeness. - Keep each pose distinct from the other eleven.
- Update the pinned emoji list in
tests/test_figure_mode.py. - To add a layout: add a key to
SHEET_LAYOUTS(every sticker number 1–12 exactly once), add the same key toSHEET_LAYOUT_IDSinbot/config.py, setSHEET_LAYOUT.
Composite mode
Section titled “Composite mode”Head expressions and their emoji are in ai_pipeline.py: EXPRESSIONS_SET_1 / EXPRESSIONS_SET_2 and
EMOJIS_SET_1 / EMOJIS_SET_2, six each, matched by index. Expressions are faces only: hands or props in the
frame inflate the head box during composition.
Which checks to run after each change
Section titled “Which checks to run after each change”| Change | Run |
|---|---|
| Outfit text or new outfit | python -m pytest tests\test_outfits.py tests\test_api.py tests\test_figure_mode.py tests\test_consistency.py |
| Preview card files | python -m pytest tests\test_outfits.py tests\test_api.py, then python tools\kiosk_check.py and python tools\kiosk_screens.py to see the cards |
| Body template | python tools\preview_template.py --id <id> (exit 0), then python -m pytest tests\test_template_m1.py tests\test_sticker_builder.py tests\test_worker_pipeline.py |
| Art style | python -m pytest tests\test_figure_mode.py tests\test_style_compare_pair.py tests\test_render_mode.py |
| Poses, emoji, layouts | python -m pytest tests\test_figure_mode.py tests\test_figure_cut.py tests\test_consistency.py tests\test_likeness_classic.py tests\test_likeness_hybrid.py |
| Anything on this page, before commit | python -m pytest |
tests/test_likeness_classic.py compares prompts with a saved reference. If it fails after a pose or style edit,
read the diff before updating the fixture: that test exists to stop accidental changes to the face prompts.
A visual judgement of real generations cannot be automated. It needs a paid run with an agreed budget; see Testing.
Where to go next
Section titled “Where to go next”- Generation pipeline — where outfit text, style and poses enter the prompt.
- Kiosk — how cards are loaded and cached.
- API reference —
GET /templatesand the preview endpoint. - Showcase packs — the landing page’s copy of outfits and packs.
- Venue requirements and branding — what can be branded for a client and what cannot.
- Cost — what a generation costs.
Last verified: 2026-11-13 against ai-sticker-pack@b715418 and ai-stickers-landing@e350ff5.