recent updates
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user