recent updates
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# Image Generation Workflow — Arising Media
|
||||
|
||||
Last updated: 2026-05-10
|
||||
Project reference: cobhamtech.com (first full run)
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Standardized process for generating, validating, and deploying AI images across all Arising Media client static sites. Every decision made in this workflow is documented so any agent or session can continue without context loss.
|
||||
|
||||
---
|
||||
|
||||
## Stack
|
||||
|
||||
API: Google Gemini (generativelanguage.googleapis.com)
|
||||
SDK: google-genai (NOT the deprecated google-generativeai package)
|
||||
Draft model: gemini-2.5-flash-image (Nano Banana — Speed Mode)
|
||||
Final model: imagen-4.0-generate-001 (Imagen 4 — Quality Mode)
|
||||
Format: JPEG, 85% quality, max 1600px wide
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Site Analysis (before any generation)
|
||||
|
||||
Before generating images, read:
|
||||
- index.html (home page structure)
|
||||
- All CSS files (understand existing color tokens, dark/light sections)
|
||||
- About, services, contact pages (identify where images add value)
|
||||
|
||||
Map each candidate image slot:
|
||||
- What HTML section will it go in?
|
||||
- Is it a CSS background-image or an inline img tag?
|
||||
- What overlay/treatment is needed for text readability?
|
||||
- What dimensions/aspect ratio does the slot require?
|
||||
|
||||
Document this in: 01-model-selection.md (image plan table)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Prompt Engineering
|
||||
|
||||
### Rules
|
||||
- Always reference the site color palette in the prompt (dark navy, slate blue, gold accents)
|
||||
- Specify "no text" and "no logos" for background images
|
||||
- Specify "photorealistic" for all marketing images
|
||||
- NO PEOPLE. NO FACES. Hardware, infrastructure, and environment only across all client sites
|
||||
- This applies to all slots: hero, about, services, contact, location — no exceptions
|
||||
- Reason: faces introduce identity/representation risk and age poorly. Hardware stays neutral and professional.
|
||||
|
||||
### Prompt structure
|
||||
[Subject] + [Environment] + [Lighting] + [Mood/Tone] + [Technical quality terms] + [Exclusions]
|
||||
|
||||
### Example (hero background)
|
||||
"Professional enterprise server room, long corridor of dark rack servers with blue LED ambient lighting, deep perspective, dark navy background, cinematic shallow depth of field, no people, photorealistic, ultra detailed"
|
||||
|
||||
### cobhamtech.com brand prompt additions
|
||||
Always append to prompts for this client:
|
||||
"dark navy and blue ambient lighting, professional, enterprise, no text"
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Generation Script Pattern
|
||||
|
||||
```python
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
client = genai.Client(api_key='KEY')
|
||||
|
||||
response = client.models.generate_images(
|
||||
model='imagen-4.0-generate-001',
|
||||
prompt='PROMPT',
|
||||
config=types.GenerateImagesConfig(
|
||||
number_of_images=1,
|
||||
aspect_ratio='16:9', # 16:9 | 4:3 | 3:2 | 1:1 | 9:16
|
||||
output_mime_type='image/jpeg',
|
||||
)
|
||||
)
|
||||
|
||||
with open('output.jpg', 'wb') as f:
|
||||
f.write(response.generated_images[0].image.image_bytes)
|
||||
```
|
||||
|
||||
Validate: file must be > 10,000 bytes. Anything smaller is an API error or empty response.
|
||||
|
||||
CRITICAL — Vision validation is mandatory before saving any image:
|
||||
The toolbox script (ai-imagen-generate.sh) automatically sends each generated image to
|
||||
gemini-2.0-flash for visual inspection. It asks: "Does this image contain people, faces,
|
||||
hands, silhouettes, or body parts?" If YES — the image is rejected, prompt is tightened,
|
||||
and generation retries up to 3 times. Only images that pass inspection are saved.
|
||||
Claude cannot visually inspect images — the vision validation step is the enforcement gate.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Placement Patterns
|
||||
|
||||
### Pattern A: CSS background-image with dark overlay (hero sections)
|
||||
|
||||
Used when: image sits behind text on a dark section
|
||||
Implementation: CSS only, no HTML change
|
||||
|
||||
```css
|
||||
.ct-hero {
|
||||
background: var(--ct-black); /* fallback */
|
||||
background-image: linear-gradient(rgba(12,15,24,0.82), rgba(12,15,24,0.92)), url('/assets/images/hero-bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
```
|
||||
|
||||
Overlay opacity guide:
|
||||
- 0.82/0.92 = subtle image visible, text fully readable
|
||||
- 0.90/0.95 = very subtle texture only
|
||||
- 0.70/0.80 = image prominent (use only if no text overlay)
|
||||
|
||||
### Pattern B: Inline img tag (editorial sections)
|
||||
|
||||
Used when: image is a standalone visual element between content sections
|
||||
Implementation: add img tag + container div
|
||||
|
||||
```html
|
||||
<div class="container" style="padding-bottom: var(--space-lg);">
|
||||
<img src="assets/images/intro-visual.jpg"
|
||||
alt="Descriptive alt text"
|
||||
style="width:100%;display:block;max-height:400px;object-fit:cover;">
|
||||
</div>
|
||||
```
|
||||
|
||||
### Pattern C: Grid column image (about/story sections)
|
||||
|
||||
Used when: image shares a row with text content
|
||||
Implementation: add img to existing grid + expand grid columns
|
||||
|
||||
```html
|
||||
<!-- Expand grid to: grid-template-columns: 1fr 1fr 420px -->
|
||||
<div>
|
||||
<img src="assets/images/about-visual.jpg"
|
||||
alt="Alt text"
|
||||
style="width:100%;display:block;border-radius:4px;">
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — CSP and nginx Updates
|
||||
|
||||
Any new image source domain requires a CSP update in nginx.conf.
|
||||
For Google Maps tiles: add `https://*.googleapis.com https://*.gstatic.com` to `img-src`
|
||||
For self-hosted images: `img-src 'self' data:` is sufficient — no change needed
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Docker Rebuild and Verify
|
||||
|
||||
After every image + HTML change:
|
||||
|
||||
```bash
|
||||
cd /home/sirdrez/arisingmedia-websites/[client]
|
||||
docker stop [container-name]
|
||||
docker rm [container-name]
|
||||
docker build -t [image-name] .
|
||||
docker run -d --name [container-name] -p [port]:80 [image-name]
|
||||
sleep 2
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:[port]/
|
||||
```
|
||||
|
||||
Verify image loads: `curl -s -o /dev/null -w "%{http_code}" http://localhost:[port]/assets/images/hero-bg.jpg`
|
||||
Expected: 200 with Content-Type: image/jpeg
|
||||
|
||||
---
|
||||
|
||||
## File Naming Convention
|
||||
|
||||
Pattern: `{page}-{slot}.jpg`
|
||||
|
||||
| Slot | File name | Aspect |
|
||||
|------|-----------|--------|
|
||||
| Home hero background | hero-bg.jpg | 16:9 |
|
||||
| Home intro visual | intro-visual.jpg | 3:2 |
|
||||
| About story | about-visual.jpg | 4:3 |
|
||||
| Services hub header | services-bg.jpg | 16:9 |
|
||||
| Contact page | contact-bg.jpg | 16:9 |
|
||||
| Location page | location-bg.jpg | 16:9 |
|
||||
|
||||
---
|
||||
|
||||
## Logging Requirement
|
||||
|
||||
Every generation run must produce a log entry in:
|
||||
`am-webdesign-sops/image-gen-workflow/02-generation-log.md`
|
||||
|
||||
Log must include: date, client, model, each image file name, prompt used, file size in bytes, placement pattern used, Docker rebuild result.
|
||||
|
||||
---
|
||||
|
||||
## Cobhamtech.com Run Reference
|
||||
|
||||
Container: cobhamtech-site
|
||||
Port: 8010
|
||||
Assets path: /home/sirdrez/arisingmedia-websites/cobhamtech.com/assets/images/
|
||||
Color tokens: --ct-black #0c0f18 / --ct-slate #1c2d42 / --ct-blue #2d5a9e / --ct-gold #c79330
|
||||
@@ -0,0 +1,89 @@
|
||||
# Image Generation Model Selection
|
||||
|
||||
Source: cutout.pro/model-comparison/imagen-vs-nanobanana + Gemini API model audit (2026-05-10)
|
||||
|
||||
---
|
||||
|
||||
## Available Models (via Google Gemini API)
|
||||
|
||||
### Imagen 4 — Quality Mode
|
||||
Model ID: `imagen-4.0-generate-001`
|
||||
Also available: `imagen-4.0-ultra-generate-001`
|
||||
|
||||
Strengths:
|
||||
- Photorealistic, high-fidelity output
|
||||
- Handles complex prompts with multi-element consistency
|
||||
- Superior text rendering inside images
|
||||
- Best for brand-critical, final-delivery assets
|
||||
|
||||
Use for:
|
||||
- Hero background images
|
||||
- Service page headers
|
||||
- Marketing and case study visuals
|
||||
- Any image that ships to production
|
||||
|
||||
---
|
||||
|
||||
### Nano Banana (Gemini 2.5 Flash Image) — Speed Mode
|
||||
Model ID: `gemini-2.5-flash-image`
|
||||
|
||||
Strengths:
|
||||
- Low latency, high volume
|
||||
- Cost-effective for rapid iteration
|
||||
- Good for concept previews and brainstorming
|
||||
|
||||
Use for:
|
||||
- Draft previews before committing to Imagen 4
|
||||
- AI chatbot or interactive UI image generation
|
||||
- Avatar or thumbnail generation at scale
|
||||
- Rapid iteration when exploring compositions
|
||||
|
||||
---
|
||||
|
||||
### Imagen 4 Fast — Budget Mode
|
||||
Model ID: `imagen-4.0-fast-generate-001`
|
||||
|
||||
Use for:
|
||||
- Quick internal previews
|
||||
- Non-public-facing visuals
|
||||
- High-volume batch jobs where quality is secondary
|
||||
|
||||
---
|
||||
|
||||
## Recommended Workflow
|
||||
|
||||
Step 1 — Draft with Speed Mode (`gemini-2.5-flash-image`)
|
||||
Generate 2-4 variations quickly. Confirm composition, subject, and tone. Low cost.
|
||||
|
||||
Step 2 — Refine with Quality Mode (`imagen-4.0-generate-001`)
|
||||
Take the winning prompt from step 1. Generate final version at full quality.
|
||||
This is the image that goes into the site.
|
||||
|
||||
Step 3 — Review against brand palette
|
||||
Check that image tones align with site color tokens:
|
||||
- cobhamtech.com: dark navy (#0c0f18), slate (#1c2d42), blue accent (#2d5a9e), gold (#c79330)
|
||||
- All hero images need to work behind dark overlays
|
||||
|
||||
Step 4 — Save to project assets
|
||||
Path convention: `assets/images/{page}-{slot}.jpg`
|
||||
Examples: `hero-bg.jpg`, `about-visual.jpg`, `services-bg.jpg`
|
||||
|
||||
---
|
||||
|
||||
## Cobhamtech.com Image Plan
|
||||
|
||||
| Slot | File | Page | Prompt Theme |
|
||||
|------|------|------|--------------|
|
||||
| Hero background | `hero-bg.jpg` | index.html | Dark server room, blue ambient lighting, depth of field |
|
||||
| About story | `about-visual.jpg` | about.html | IT professional at clean desk, dual monitors, neutral dark background |
|
||||
| Services hub | `services-bg.jpg` | services/index.html | Enterprise network infrastructure, abstract, dark |
|
||||
| Intro visual | `intro-visual.jpg` | index.html | Business and technology handshake, professional setting |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Never use Nano Banana for final production images on client sites
|
||||
- Imagen 4 Ultra adds marginal quality gain over standard — not worth the cost for web assets
|
||||
- All images should be exported as JPEG at 85% quality, max 1600px wide, for web performance
|
||||
- Run generated images through the site CSP — ensure `img-src` allows `self` and `data:` only (no external CDN hotlinking)
|
||||
@@ -0,0 +1,71 @@
|
||||
# Image Generation Log — CobhamTech.com
|
||||
|
||||
**Date:** 2026-05-10
|
||||
**Model:** imagen-4.0-generate-001 (Gemini Imagen 4)
|
||||
**SDK:** google-genai (Python)
|
||||
**API Key:** AIzaSyD-njx1-hyqnazckGTJ6SnMJ8o_B2C0UsI
|
||||
**Script:** generate_images.py (deleted after run)
|
||||
|
||||
---
|
||||
|
||||
## Images Generated
|
||||
|
||||
### hero-bg.jpg
|
||||
- **Prompt:** Professional enterprise server room, long corridor of dark rack servers with blue LED ambient lighting, deep perspective, dark navy background, cinematic shallow depth of field, no people, photorealistic, ultra detailed
|
||||
- **Aspect ratio:** 16:9
|
||||
- **File size:** 395,927 bytes
|
||||
- **Placement:** .ct-hero background-image in assets/css/page-home.css — overlay gradient rgba(12,15,24,0.82) to rgba(12,15,24,0.92), background-size cover
|
||||
- **Status:** OK
|
||||
|
||||
### about-visual.jpg
|
||||
- **Prompt:** Professional IT consultant at a clean modern workstation, dual monitors displaying network diagrams and dashboards, dark office with subtle blue ambient lighting, business attire, confident expression, photorealistic
|
||||
- **Aspect ratio:** 4:3
|
||||
- **File size:** 426,565 bytes
|
||||
- **Placement:** about.html ct-about-story section — third column, grid-template-columns updated to 1fr 1fr 420px, img tag with border-radius 4px
|
||||
- **Status:** OK
|
||||
|
||||
### services-bg.jpg
|
||||
- **Prompt:** Abstract enterprise technology network, dark background, glowing blue interconnected nodes and data pathways, minimal high-tech aesthetic, no text, no people, cinematic, photorealistic render
|
||||
- **Aspect ratio:** 16:9
|
||||
- **File size:** 403,142 bytes
|
||||
- **Placement:** .ct-svc-idx-hero background-image in assets/css/page-services-index.css — same overlay pattern as hero-bg
|
||||
- **Status:** OK
|
||||
|
||||
### intro-visual.jpg
|
||||
- **Prompt:** Business professional and IT consultant collaborating at a modern conference table with laptops and tablets, professional corporate office, clean neutral dark background, photorealistic, teamwork and trust
|
||||
- **Aspect ratio:** 4:3 (retried — original 3:2 not supported)
|
||||
- **File size:** 373,852 bytes
|
||||
- **Placement:** index.html — div.container block between ct-intro section and ct-home-sec-services, max-height 400px object-fit cover
|
||||
- **Status:** OK
|
||||
|
||||
---
|
||||
|
||||
## API Errors / Retries
|
||||
|
||||
- intro-visual.jpg failed on first attempt with aspect ratio 3:2: `aspectRatio 3:2 is not supported. Supported values are 1:1, 9:16, 16:9, 4:3, 3:4.`
|
||||
- Retried with 4:3. Succeeded.
|
||||
|
||||
## Supported Aspect Ratios (Imagen 4)
|
||||
|
||||
1:1, 9:16, 16:9, 4:3, 3:4
|
||||
|
||||
3:2 is NOT supported. Use 4:3 as the closest substitute for landscape-medium compositions.
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
- Rebuilt cobhamtech-static image from scratch after HTML/CSS changes
|
||||
- Container running on port 8010
|
||||
- All 4 images confirmed HTTP 200 at runtime
|
||||
- Homepage HTTP 200
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. Imagen 4 does not support 3:2 aspect ratio. The supported set is: 1:1, 9:16, 16:9, 4:3, 3:4. Always validate aspect ratios before scripting a batch.
|
||||
2. Generation of 4 images (3 x 16:9, 1 x 4:3) completed in under 90 seconds total.
|
||||
3. Dark overlay gradients (rgba at 0.82-0.92 opacity) are necessary on these photorealistic images to maintain text legibility against white hero text.
|
||||
4. File sizes ranged 374KB-427KB for JPEG output at these aspect ratios — appropriate for web use without additional compression.
|
||||
5. The google-genai SDK uses `client.models.generate_images()` with a `GenerateImagesConfig` object — not the `generate_content()` path.
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"file": "hero-bg.jpg",
|
||||
"aspect": "16:9",
|
||||
"prompt": "Long corridor of enterprise server racks in a dark data center, blue and white LED indicator lights blinking on rack units, cable management arms, deep perspective vanishing point, dark navy ambient lighting, no people, no humans, hardware only, photorealistic, 8K detail"
|
||||
},
|
||||
{
|
||||
"file": "about-visual.jpg",
|
||||
"aspect": "4:3",
|
||||
"prompt": "Dense fiber optic patch panel with multicolored LC connectors and cables, server rack mounted in data center, LED status lights green and blue, dark background, close-up macro shot, no people, no hands, hardware only, photorealistic, sharp focus"
|
||||
},
|
||||
{
|
||||
"file": "services-bg.jpg",
|
||||
"aspect": "16:9",
|
||||
"prompt": "Overhead view of enterprise network switches and routers mounted in open server rack, Ethernet cables organized in bundles, blue port indicator lights, dark equipment, clean cable management, no people, hardware only, photorealistic, professional data center"
|
||||
},
|
||||
{
|
||||
"file": "intro-visual.jpg",
|
||||
"aspect": "4:3",
|
||||
"prompt": "Cisco network switches and firewall appliances in a wall-mounted server cabinet, blinking LED activity lights, dark navy background, organized cable bundles, cooling vents visible, no people, no human presence, hardware only, photorealistic, enterprise IT infrastructure"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Select and regenerate hero images — Carbon Fiber Support CMS.
|
||||
|
||||
- Click a thumbnail to select it (gold border = selected)
|
||||
- Click Regen under any thumbnail to regenerate just that variant
|
||||
- Save → writes selections.json for Webflow upload
|
||||
|
||||
Usage:
|
||||
python3 select_hero_images.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
SOURCED_DIR = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/image-rendering/Application_Problems/grounded/sourced")
|
||||
GENERATOR = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/generate_sourced_photoreal.py")
|
||||
ENV_FILE = Path("/home/sirdrez/Downloads/Carbon Fiber Support_PDF/.env")
|
||||
SELECTIONS_OUT = Path(__file__).parent / "selections.json"
|
||||
|
||||
APPLICATIONS = [
|
||||
("bowing-basement-wall-repair", "Bowing Basement Wall Repair"),
|
||||
("horizontal-basement-wall-cracks", "Horizontal Basement Wall Cracks"),
|
||||
("parking-garage-column-wrapping", "Parking Garage Column Wrapping"),
|
||||
("bridge-girder-strengthening", "Bridge Girder Strengthening"),
|
||||
("stair-step-foundation-cracks", "Stair-Step Foundation Cracks"),
|
||||
("parking-garage-deck-repair", "Parking Garage Deck Repair"),
|
||||
("vertical-foundation-cracks", "Vertical Foundation Cracks"),
|
||||
("poured-concrete-wall-repair", "Poured Concrete Wall Repair"),
|
||||
("interior-block-wall-bulging", "Interior Block Wall Bulging"),
|
||||
("foundation-wall-repair", "Foundation Wall Repair"),
|
||||
("crawlspace-wall-reinforcement", "Crawlspace Wall Reinforcement"),
|
||||
("cracked-concrete-slab-repair", "Cracked Concrete Slab Repair"),
|
||||
("corner-crack-repair", "Corner Crack Repair"),
|
||||
("concrete-block-wall-repair", "Concrete Block Wall Repair"),
|
||||
("residential-retaining-wall-repair", "Residential Retaining Wall Repair"),
|
||||
("commercial-retaining-wall-repair", "Commercial Retaining Wall Repair"),
|
||||
("commercial-building-column-reinforcement", "Commercial Building Column Reinforcement"),
|
||||
("warehouse-roof-truss-repair", "Warehouse Roof Truss Repair"),
|
||||
("warehouse-beam-strengthening", "Warehouse Beam Strengthening"),
|
||||
("parking-garage-beam-strengthening", "Parking Garage Beam Strengthening"),
|
||||
("fire-and-impact-damage-beam-repair", "Fire and Impact Damage Beam Repair"),
|
||||
("concrete-foundation-beam-repair", "Concrete Foundation Beam Repair"),
|
||||
("bridge-column-and-pier-repair", "Bridge Column and Pier Repair"),
|
||||
]
|
||||
|
||||
VARIANTS = ["v1", "v2", "v3", "v4"]
|
||||
V_LABELS = ["v1 24mm", "v2 macro", "v3 3/4", "v4 alt"]
|
||||
THUMB_W, THUMB_H = 190, 143
|
||||
|
||||
BG = "#0e0e0e"
|
||||
ROW_EVEN = "#141414"
|
||||
ROW_ODD = "#111111"
|
||||
SEL_CLR = "#ffc107"
|
||||
DIM_CLR = "#555555"
|
||||
REGEN_BG = "#2a1a00"
|
||||
REGEN_FG = "#ff9800"
|
||||
BUSY_CLR = "#ff5722"
|
||||
OK_CLR = "#4caf50"
|
||||
TEXT_CLR = "#ffffff"
|
||||
|
||||
PLACEHOLDER = None # lazy-loaded gray image
|
||||
|
||||
|
||||
def _load_env() -> dict:
|
||||
env = os.environ.copy()
|
||||
if ENV_FILE.exists():
|
||||
for line in ENV_FILE.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
env[k.strip()] = v.strip()
|
||||
return env
|
||||
|
||||
|
||||
def _gray_placeholder(w: int, h: int) -> Image.Image:
|
||||
img = Image.new("RGB", (w, h), "#1a1a1a")
|
||||
return img
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self, root: tk.Tk):
|
||||
self.root = root
|
||||
self.root.title("CFS Image Selector")
|
||||
self.root.configure(bg=BG)
|
||||
self.root.geometry("1030x840")
|
||||
|
||||
self.selections: dict[str, int] = {s: 0 for s, _ in APPLICATIONS}
|
||||
self._tk_imgs: dict = {}
|
||||
self._img_lbls: dict = {} # (slug, v_idx) -> Label (image widget)
|
||||
self._bdr_lbls: dict = {} # (slug, v_idx) -> same Label for border
|
||||
self._txt_lbls: dict = {} # (slug, v_idx) -> variant text Label
|
||||
self._regen_btns: dict = {} # (slug, v_idx) -> regen Button
|
||||
self._busy: set = set() # (slug, v_idx) currently regenerating
|
||||
|
||||
self._build()
|
||||
self._load_selections()
|
||||
|
||||
# ------------------------------------------------------------------ build
|
||||
|
||||
def _build(self):
|
||||
bar = tk.Frame(self.root, bg=BG, pady=10)
|
||||
bar.pack(fill="x", padx=20)
|
||||
tk.Label(bar, text="Carbon Fiber Support — Select Hero Image",
|
||||
bg=BG, fg=TEXT_CLR, font=("Helvetica", 12, "bold")).pack(side="left")
|
||||
tk.Button(bar, text="Save Selections", command=self._save,
|
||||
bg="#1a3a1a", fg=OK_CLR, relief="flat", padx=14, pady=6,
|
||||
font=("Helvetica", 11, "bold"), cursor="hand2",
|
||||
activebackground="#1f4a1f").pack(side="right", padx=(6, 0))
|
||||
tk.Button(bar, text="All v1", command=lambda: self._select_all(0),
|
||||
bg="#1a2a3a", fg="#64b5f6", relief="flat", padx=10, pady=6,
|
||||
font=("Helvetica", 10), cursor="hand2").pack(side="right")
|
||||
|
||||
self._status = tk.StringVar(value="v1 pre-selected for all — click to change — Regen to regenerate")
|
||||
tk.Label(self.root, textvariable=self._status, bg=BG, fg=DIM_CLR,
|
||||
font=("Helvetica", 10), anchor="w").pack(fill="x", padx=20, pady=(0, 4))
|
||||
|
||||
outer = tk.Frame(self.root, bg=BG)
|
||||
outer.pack(fill="both", expand=True, padx=8, pady=(0, 8))
|
||||
|
||||
canvas = tk.Canvas(outer, bg=BG, highlightthickness=0)
|
||||
vsb = tk.Scrollbar(outer, orient="vertical", command=canvas.yview)
|
||||
canvas.configure(yscrollcommand=vsb.set)
|
||||
vsb.pack(side="right", fill="y")
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
|
||||
self._inner = tk.Frame(canvas, bg=BG)
|
||||
win_id = canvas.create_window((0, 0), window=self._inner, anchor="nw")
|
||||
canvas.bind("<Configure>", lambda e: canvas.itemconfig(win_id, width=e.width))
|
||||
self._inner.bind("<Configure>",
|
||||
lambda _: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.bind_all("<Button-4>", lambda _: canvas.yview_scroll(-1, "units"))
|
||||
canvas.bind_all("<Button-5>", lambda _: canvas.yview_scroll(1, "units"))
|
||||
canvas.bind_all("<MouseWheel>",
|
||||
lambda e: canvas.yview_scroll(int(-1 * e.delta / 120), "units"))
|
||||
|
||||
for idx, (slug, name) in enumerate(APPLICATIONS):
|
||||
self._build_row(idx, slug, name)
|
||||
|
||||
def _build_row(self, idx: int, slug: str, name: str):
|
||||
bg = ROW_EVEN if idx % 2 == 0 else ROW_ODD
|
||||
row = tk.Frame(self._inner, bg=bg, pady=8, padx=12)
|
||||
row.pack(fill="x", pady=1)
|
||||
|
||||
tk.Label(row, text=f"{idx+1:02d} {name}", bg=bg, fg=TEXT_CLR,
|
||||
font=("Helvetica", 10, "bold"), anchor="w", width=30).pack(side="left", padx=(0, 10))
|
||||
|
||||
for v_idx, (vk, vl) in enumerate(zip(VARIANTS, V_LABELS)):
|
||||
path = SOURCED_DIR / f"{slug}_{vk}.jpg"
|
||||
cell = tk.Frame(row, bg=bg)
|
||||
cell.pack(side="left", padx=3)
|
||||
|
||||
# Image label
|
||||
tk_img = self._make_tk_img(path)
|
||||
self._tk_imgs[(slug, v_idx)] = tk_img
|
||||
is_sel = (v_idx == 0)
|
||||
border = SEL_CLR if is_sel else "#2a2a2a"
|
||||
lbl = tk.Label(cell, image=tk_img,
|
||||
highlightthickness=4, highlightbackground=border,
|
||||
cursor="hand2", bg="#000")
|
||||
lbl.pack()
|
||||
self._img_lbls[(slug, v_idx)] = lbl
|
||||
self._bdr_lbls[(slug, v_idx)] = lbl
|
||||
lbl.bind("<Button-1>", lambda e, s=slug, vi=v_idx: self._select(s, vi))
|
||||
lbl.bind("<Enter>", lambda e, s=slug, vi=v_idx: self._hover(s, vi, True))
|
||||
lbl.bind("<Leave>", lambda e, s=slug, vi=v_idx: self._hover(s, vi, False))
|
||||
|
||||
# Variant label row: text + regen button side by side
|
||||
foot = tk.Frame(cell, bg=bg)
|
||||
foot.pack(fill="x")
|
||||
txt = tk.Label(foot, text=vl, bg=bg,
|
||||
fg=SEL_CLR if is_sel else DIM_CLR,
|
||||
font=("Helvetica", 9), anchor="w")
|
||||
txt.pack(side="left")
|
||||
self._txt_lbls[(slug, v_idx)] = txt
|
||||
|
||||
rbtn = tk.Button(foot, text="Regen",
|
||||
command=lambda s=slug, vi=v_idx: self._regen(s, vi),
|
||||
bg=REGEN_BG, fg=REGEN_FG, relief="flat",
|
||||
font=("Helvetica", 8), padx=5, pady=1,
|
||||
cursor="hand2", activebackground="#3a2500")
|
||||
rbtn.pack(side="right")
|
||||
self._regen_btns[(slug, v_idx)] = rbtn
|
||||
|
||||
# ------------------------------------------------------------------ logic
|
||||
|
||||
def _make_tk_img(self, path: Path) -> ImageTk.PhotoImage:
|
||||
if path.exists():
|
||||
try:
|
||||
img = Image.open(path)
|
||||
img.thumbnail((THUMB_W, THUMB_H), Image.LANCZOS)
|
||||
return ImageTk.PhotoImage(img)
|
||||
except Exception:
|
||||
pass
|
||||
return ImageTk.PhotoImage(_gray_placeholder(THUMB_W, THUMB_H))
|
||||
|
||||
def _select(self, slug: str, v_idx: int):
|
||||
old = self.selections[slug]
|
||||
if old == v_idx:
|
||||
return
|
||||
lbl_old = self._bdr_lbls.get((slug, old))
|
||||
if lbl_old:
|
||||
lbl_old.configure(highlightbackground="#2a2a2a")
|
||||
txt_old = self._txt_lbls.get((slug, old))
|
||||
if txt_old:
|
||||
txt_old.configure(fg=DIM_CLR)
|
||||
|
||||
lbl_new = self._bdr_lbls.get((slug, v_idx))
|
||||
if lbl_new:
|
||||
lbl_new.configure(highlightbackground=SEL_CLR)
|
||||
txt_new = self._txt_lbls.get((slug, v_idx))
|
||||
if txt_new:
|
||||
txt_new.configure(fg=SEL_CLR)
|
||||
|
||||
self.selections[slug] = v_idx
|
||||
self._status.set(f"Selected {slug} → {VARIANTS[v_idx]}")
|
||||
|
||||
def _hover(self, slug: str, v_idx: int, entering: bool):
|
||||
if self.selections[slug] == v_idx or (slug, v_idx) in self._busy:
|
||||
return
|
||||
lbl = self._bdr_lbls.get((slug, v_idx))
|
||||
if lbl:
|
||||
lbl.configure(highlightbackground="#555" if entering else "#2a2a2a")
|
||||
|
||||
def _select_all(self, v_idx: int):
|
||||
for slug, _ in APPLICATIONS:
|
||||
self._select(slug, v_idx)
|
||||
self._status.set(f"All applications set to {VARIANTS[v_idx]}")
|
||||
|
||||
# ------------------------------------------------------------------ regen
|
||||
|
||||
def _regen(self, slug: str, v_idx: int):
|
||||
key = (slug, v_idx)
|
||||
if key in self._busy:
|
||||
return
|
||||
self._busy.add(key)
|
||||
|
||||
# Delete the file so the generator recreates it
|
||||
path = SOURCED_DIR / f"{slug}_{VARIANTS[v_idx]}.jpg"
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
# Visual: busy state
|
||||
lbl = self._bdr_lbls.get(key)
|
||||
if lbl:
|
||||
lbl.configure(highlightbackground=BUSY_CLR)
|
||||
btn = self._regen_btns.get(key)
|
||||
if btn:
|
||||
btn.configure(text="...", state="disabled")
|
||||
txt = self._txt_lbls.get(key)
|
||||
if txt:
|
||||
txt.configure(fg=BUSY_CLR)
|
||||
|
||||
self._status.set(f"Regenerating {slug} {VARIANTS[v_idx]} ...")
|
||||
|
||||
def run():
|
||||
env = _load_env()
|
||||
env["SLUG_FILTER_VARIANT"] = VARIANTS[v_idx]
|
||||
subprocess.run(
|
||||
["python3", str(GENERATOR), slug],
|
||||
env=env, capture_output=True
|
||||
)
|
||||
self.root.after(0, lambda: self._regen_done(slug, v_idx))
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def _regen_done(self, slug: str, v_idx: int):
|
||||
key = (slug, v_idx)
|
||||
self._busy.discard(key)
|
||||
|
||||
path = SOURCED_DIR / f"{slug}_{VARIANTS[v_idx]}.jpg"
|
||||
tk_img = self._make_tk_img(path)
|
||||
self._tk_imgs[key] = tk_img
|
||||
lbl = self._img_lbls.get(key)
|
||||
if lbl:
|
||||
lbl.configure(image=tk_img)
|
||||
|
||||
is_sel = (self.selections[slug] == v_idx)
|
||||
border = SEL_CLR if is_sel else "#2a2a2a"
|
||||
bdr = self._bdr_lbls.get(key)
|
||||
if bdr:
|
||||
bdr.configure(highlightbackground=border)
|
||||
txt = self._txt_lbls.get(key)
|
||||
if txt:
|
||||
txt.configure(fg=SEL_CLR if is_sel else DIM_CLR)
|
||||
btn = self._regen_btns.get(key)
|
||||
if btn:
|
||||
btn.configure(text="Regen", state="normal")
|
||||
|
||||
self._status.set(f"Done {slug} {VARIANTS[v_idx]}")
|
||||
|
||||
# ------------------------------------------------------------------ save
|
||||
|
||||
def _save(self):
|
||||
out = {slug: VARIANTS[vi] for slug, vi in self.selections.items()}
|
||||
SELECTIONS_OUT.write_text(json.dumps(out, indent=2))
|
||||
self._status.set(f"Saved {len(out)} selections → {SELECTIONS_OUT.name}")
|
||||
|
||||
def _load_selections(self):
|
||||
if SELECTIONS_OUT.exists():
|
||||
try:
|
||||
data = json.loads(SELECTIONS_OUT.read_text())
|
||||
for slug, vk in data.items():
|
||||
if vk in VARIANTS:
|
||||
self._select(slug, VARIANTS.index(vk))
|
||||
self._status.set(f"Loaded {SELECTIONS_OUT.name}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
App(root)
|
||||
root.mainloop()
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"source": "https://ai.google.dev/gemini-api/docs/imagen",
|
||||
"retrieved": "2026-05-13",
|
||||
"sdk_package": "google-genai",
|
||||
"sdk_import": "from google import genai\nfrom google.genai import types",
|
||||
|
||||
"models": [
|
||||
{
|
||||
"id": "imagen-4.0-generate-001",
|
||||
"label": "Imagen 4 Standard",
|
||||
"use_case": "Production — best balance of quality and speed",
|
||||
"supports_image_size": true
|
||||
},
|
||||
{
|
||||
"id": "imagen-4.0-ultra-generate-001",
|
||||
"label": "Imagen 4 Ultra",
|
||||
"use_case": "Highest quality output, slower — hero images and print",
|
||||
"supports_image_size": true
|
||||
},
|
||||
{
|
||||
"id": "imagen-4.0-fast-generate-001",
|
||||
"label": "Imagen 4 Fast",
|
||||
"use_case": "Drafts and rapid iteration — low latency",
|
||||
"supports_image_size": false
|
||||
}
|
||||
],
|
||||
|
||||
"deprecated_models": [
|
||||
{ "id": "imagen-3.0-generate-001", "status": "discontinued" }
|
||||
],
|
||||
|
||||
"method": "client.models.generate_images",
|
||||
"rest_endpoint": "https://generativelanguage.googleapis.com/v1beta/models/{model}:predict",
|
||||
"auth_header": "x-goog-api-key",
|
||||
|
||||
"parameters": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"values": ["imagen-4.0-generate-001", "imagen-4.0-ultra-generate-001", "imagen-4.0-fast-generate-001"]
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"language": "English only",
|
||||
"max_tokens": 480,
|
||||
"notes": "Text overlays in images: keep under 25 characters for best results. Exact font replication not guaranteed."
|
||||
},
|
||||
"config": {
|
||||
"class": "types.GenerateImagesConfig",
|
||||
"fields": {
|
||||
"number_of_images": {
|
||||
"type": "integer",
|
||||
"min": 1,
|
||||
"max": 4,
|
||||
"default": 4
|
||||
},
|
||||
"aspect_ratio": {
|
||||
"type": "string",
|
||||
"default": "1:1",
|
||||
"values": ["1:1", "3:4", "4:3", "9:16", "16:9"],
|
||||
"notes": "Do NOT use '3:2' — not supported and will error"
|
||||
},
|
||||
"image_size": {
|
||||
"type": "string",
|
||||
"default": "1K",
|
||||
"values": ["1K", "2K"],
|
||||
"applies_to": ["imagen-4.0-generate-001", "imagen-4.0-ultra-generate-001"],
|
||||
"not_available_for": ["imagen-4.0-fast-generate-001"]
|
||||
},
|
||||
"person_generation": {
|
||||
"type": "string",
|
||||
"default": "allow_adult",
|
||||
"values": [
|
||||
{ "value": "dont_allow", "description": "No people or faces in output — use for hardware, product, landscape" },
|
||||
{ "value": "allow_adult", "description": "Adults only" },
|
||||
{ "value": "allow_all", "description": "Adults and children — restricted in EU, UK, CH, MENA regions" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"output": {
|
||||
"watermark": "SynthID — embedded in all generated images, not visible",
|
||||
"format": "PIL Image object (SDK) / base64 bytes (REST)",
|
||||
"access_sdk": "response.generated_images[i].image"
|
||||
},
|
||||
|
||||
"python_minimal_example": "from google import genai\nfrom google.genai import types\n\nclient = genai.Client()\nresponse = client.models.generate_images(\n model='imagen-4.0-generate-001',\n prompt='Your prompt here',\n config=types.GenerateImagesConfig(\n number_of_images=4,\n aspect_ratio='16:9',\n person_generation='dont_allow'\n )\n)\nfor img in response.generated_images:\n img.image.show()",
|
||||
|
||||
"rest_minimal_example": "curl -X POST 'https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-generate-001:predict' -H 'x-goog-api-key: $GEMINI_API_KEY' -H 'Content-Type: application/json' -d '{\"instances\":[{\"prompt\":\"Your prompt\"}],\"parameters\":{\"sampleCount\":4}}'",
|
||||
|
||||
"arising_media_defaults": {
|
||||
"draft_model": "imagen-4.0-fast-generate-001",
|
||||
"production_model": "imagen-4.0-generate-001",
|
||||
"hero_model": "imagen-4.0-ultra-generate-001",
|
||||
"person_generation": "dont_allow",
|
||||
"number_of_images": 4,
|
||||
"aspect_ratio_web_hero": "16:9",
|
||||
"aspect_ratio_square": "1:1",
|
||||
"aspect_ratio_portrait": "3:4",
|
||||
"file_naming": "{page}-{slot}.jpg",
|
||||
"workflow": "draft with fast → select variant → regenerate with standard or ultra"
|
||||
},
|
||||
|
||||
"prompt_engineering_notes": [
|
||||
"Describe subject, environment, lighting, and mood in one sentence",
|
||||
"Photorealistic hardware/landscape: add 'photorealistic, 4K, professional photography'",
|
||||
"Avoid people/faces: include 'no people, no humans' explicitly when using dont_allow is not enough",
|
||||
"Camera style modifiers: 'shot on Canon 5D', 'wide angle lens', 'golden hour lighting'",
|
||||
"Art style: 'architectural render', 'flat illustration', 'watercolor wash'",
|
||||
"Keep text overlays short: 25 chars max, specify position ('top left', 'centered')"
|
||||
],
|
||||
|
||||
"known_errors": [
|
||||
{
|
||||
"error": "aspect ratio X:X not supported",
|
||||
"cause": "3:2 or other non-standard ratio passed",
|
||||
"fix": "Use only: 1:1, 3:4, 4:3, 9:16, 16:9"
|
||||
},
|
||||
{
|
||||
"error": "imageSize not applicable",
|
||||
"cause": "imageSize passed to imagen-4.0-fast-generate-001",
|
||||
"fix": "Remove imageSize parameter when using fast model"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user