Skip to content

Outfits, templates and art styles

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 in config.ART_STYLE_IDS, selected by the ART_STYLE variable.
  • 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.

@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 marker TEMPLATE_DEFAULT).
  • A job whose template_id is not in the catalogue fails immediately, without retries. Do not remove an entry while jobs that use it are queued.
  • POST /session/{token}/template rejects unknown ids and hidden placeholders with 422.
  • GET /templates/{id}.png serves only files referenced by the catalogue, never the whole folder.

An outfit needs: one constant with the text, one Template line, two preview files.

  1. 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.
  2. Add the entry to TEMPLATES at 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" with head_anchor=M1_HEAD_ANCHOR keeps the composite fallback working: if the figure sheet fails, the guest still gets a pack, wearing the default body.

  3. Create the preview card. Two files per outfit:

    File Format Limit In git
    templates/preview_src/<id>.webp 1024 × 1024 WebP, source ≤ 250 KB yes
    templates/<id>_preview.webp (the preview field) 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_text only — no guest photo. --only u6 limits it, --force redraws existing ones.
    --candidates N yes N variants per outfit into out/outfit_previews/<id>_cand<k>.webp; installs the first.
    --character yes Draws a waist-up fictional character instead of a headless torso; the source for --neckdown.
    --pick u6=2 no Installs candidate 2 for u6 from files already on disk.
    --neckdown no 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’s NECKDOWN_CUT table.
    --recompress no 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-image is 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=2
    python tools\make_outfit_previews.py --recompress

    A card must show clothes only — a fictional torso, never a real person.

  4. Update the pinned order in tests: READY in tests/test_outfits.py and the two id lists in tests/test_api.py.

  5. Run the checks from the table at the end of this page.

  6. Tell the landing page. Its mock kiosk mirrors the real outfit set and order; see Showcase packs.

  1. Commit the catalogue change, both preview files and the updated tests (push.ps1 deploys only committed files).
  2. 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.
  3. Check https://stickers.eventtalks.uz/templates: the new id is listed in the intended position, and its preview URL returns the image.
  4. Open the kiosk, reach the outfit screen and confirm the card.
  5. 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.
  6. 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 /templates returns visible_templates(...): entries with ready=True, in list order. Ready outfits are listed first, placeholders last.
  • SHOW_PLACEHOLDER_TEMPLATES=1 also returns entries with ready=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.

A body template is needed only when composite mode must show a body other than the default one.

  1. Draw the body: a PNG on a flat green background (#00FF00), with a flat magenta circle (#FF00FF) exactly where the head will sit.

  2. Make sure the id exists. The tool looks up TEMPLATES_BY_ID[<id>].file and 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.py recreates placeholder files.

  3. 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>.png and templates/<id>_template.json (anchor and collar line), keeps the previous file once as <id>_placeholder_backup.png / .json, and prints JSON with head_anchor.

  4. Copy head_anchor from the output into the catalogue entry.

  5. Preview on real heads:

    Terminal window
    python tools\preview_template.py --id <id> --sheet <head_sheet>.png
    python tools\preview_template.py --id <id> --sheet <head_sheet>.png --variants

    Without --sheet the tool takes the reference sheet of the latest job in bot\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. --variants adds rows for head scales 1.35 / 1.55 / 1.75 with a matching vertical shift.

  6. Tune head_scale, head_dy, head_dx in the catalogue and repeat step 5 until the proportions are right.

  7. Set ready=True and give the entry a label, an outfit_text (so figure mode draws matching clothes) and a preview.

  8. Never commit preview_*.png. They contain guest faces and are git-ignored.

Styles apply to figure mode. The style of a job is fixed when the job is created (jobs.art_style).

  1. Add a preset to ART_STYLES in bot/services/ai_pipeline.py:

    Key Content
    label Short human-readable name.
    style The rendering style sentence inserted as Style: … .
    chest Head-to-figure proportion rule for chest-up frames.
    closeup Proportion rule for close-up frames.
  2. Register the id in ART_STYLE_IDS in bot/config.py, in the same position as in the dictionary. Start-up validation uses this tuple; an ART_STYLE value outside it stops the process.

  3. Update the pinned list in tests/test_figure_mode.py (test_art_styles_are_separate_blocks): it asserts that the dictionary keys, ART_STYLE_IDS and a literal tuple are identical, and that every style produces a different prompt.

  4. Select it: ART_STYLE=<id> in bot/.env, then restart both processes (the API writes the style into the job, the worker renders it).

  5. 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-image is 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.

  6. 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.

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: three chest, then three closeup. Sticker n uses FIGURE_FRAMES[(n - 1) % 6].

  • SHEET_LAYOUTS — how the 12 stickers are spread over sheets:

    Layout Sheets Grid per sheet Aspect
    2x6 2 × 6 figures 3 columns × 2 rows FIGURE_SHEET_ASPECT
    3x4 3 × 4 figures 2 × 2 1:1

    In both layouts every sheet has chest-up frames in the top row and close-ups in the bottom row.

Steps:

  1. Edit the pose text and emoji in place; keep 6 + 6 entries.
  2. Put gestures with hands on chest positions (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.
  3. Keep each pose distinct from the other eleven.
  4. Update the pinned emoji list in tests/test_figure_mode.py.
  5. To add a layout: add a key to SHEET_LAYOUTS (every sticker number 1–12 exactly once), add the same key to SHEET_LAYOUT_IDS in bot/config.py, set SHEET_LAYOUT.

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.

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.


Last verified: 2026-11-13 against ai-sticker-pack@b715418 and ai-stickers-landing@e350ff5.