Spaces:
Running
Running
| """Background analysis: location, atmosphere, and lighting from image + WD14 tags. | |
| Uses WD14 tags as the primary source (they already detect many background | |
| and lighting tags) and supplements with lightweight color-based heuristics | |
| that run on the image border regions. | |
| All outputs are Danbooru-format tags (underscores). | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| # Location-related tags from tag pools | |
| _BG_LOCATION_KEYWORDS = { | |
| "indoor": {"indoor", "room", "bedroom", "kitchen", "classroom", "library", | |
| "indoors", "interior", "hallway", "bathroom", "toilet", "office", | |
| "shop", "store", "staircase", "elevator", "laboratory"}, | |
| "outdoor": {"outdoor", "sky", "cloud", "sun", "sunset", "sunrise", | |
| "nature", "forest", "tree", "mountain", "beach", "sea", | |
| "ocean", "river", "lake", "waterfall", "field", "meadow", | |
| "desert", "ruins", "castle", "village", "street", "city", | |
| "rooftop", "bridge", "park", "garden", "path", "road"}, | |
| "special": {"underwater", "space", "cyberpunk", "fantasy", "surreal"}, | |
| } | |
| _BG_ATMOSPHERE_KEYWORDS = { | |
| "weather": {"rain", "snow", "fog", "mist", "wind", "storm", "cloudy", | |
| "clear", "starry", "aurora"}, | |
| "mood": {"atmospheric", "ethereal", "dreamlike", "romantic", | |
| "melancholic", "serene", "dark", "moody", "dramatic", | |
| "cozy", "calm", "chaotic", "peaceful"}, | |
| "effects": {"petals", "falling leaves", "fireflies", "sparkles", | |
| "glow", "light rays", "particles"}, | |
| } | |
| _BG_LIGHTING_KEYWORDS = { | |
| "natural": {"sunlight", "sunrise", "sunset", "moonlight", "starlight", | |
| "candlelight", "natural lighting", "golden hour"}, | |
| "artificial": {"neon", "fluorescent", "LED", "lamp", "candle", "torch"}, | |
| "style": {"cinematic lighting", "soft lighting", "hard lighting", | |
| "volumetric", "rim lighting", "backlighting", "sidelight", | |
| "god rays", "crepuscular", "studio lighting", "flash", | |
| "warm lighting", "cool lighting", "dramatic lighting", | |
| "moody lighting", "dark lighting", "spotlight"}, | |
| } | |
| def analyze_background(img_arr: np.ndarray, wd14_tags: list[str]) -> list[str]: | |
| """Analyze background/atmosphere from image + WD14 tags. | |
| Args: | |
| img_arr: RGB image as numpy array (H, W, 3 uint8). | |
| wd14_tags: General tags from WD14 ensemble. | |
| Returns: | |
| List of Danbooru-style background/atmosphere tags. | |
| """ | |
| tags: set[str] = set() | |
| # --- Pass-through WD14 tags that match background keywords --- | |
| wd14_lower = {t.lower() for t in wd14_tags} | |
| all_keywords = set() | |
| for kw_set in (_BG_LOCATION_KEYWORDS, _BG_ATMOSPHERE_KEYWORDS, _BG_LIGHTING_KEYWORDS): | |
| for keywords in kw_set.values(): | |
| all_keywords.update(keywords) | |
| for tag in wd14_tags: | |
| tag_lower = tag.lower().replace("_", " ") | |
| for kw in all_keywords: | |
| if kw in tag_lower: | |
| tags.add(tag) | |
| break | |
| # --- Lightweight color analysis on border region --- | |
| if img_arr is not None and img_arr.size > 0 and img_arr.ndim == 3: | |
| color_tags = _analyze_border_color(img_arr) | |
| tags.update(color_tags) | |
| return sorted(tags) if tags else [] | |
| def _analyze_border_color(img_arr: np.ndarray) -> set[str]: | |
| """Analyze border region for color temperature and lighting cues.""" | |
| tags: set[str] = set() | |
| h, w = img_arr.shape[:2] | |
| if h < 10 or w < 10: | |
| return tags | |
| border_size = min(h, w) // 16 | |
| border_size = max(border_size, 5) | |
| # Sample border pixels (top, bottom, left, right) | |
| border_px = np.concatenate([ | |
| img_arr[:border_size, :, :].reshape(-1, 3), | |
| img_arr[-border_size:, :, :].reshape(-1, 3), | |
| img_arr[:, :border_size, :].reshape(-1, 3), | |
| img_arr[:, -border_size:, :].reshape(-1, 3), | |
| ], axis=0) | |
| if len(border_px) == 0: | |
| return tags | |
| # Subsample for speed | |
| if len(border_px) > 500: | |
| idx = np.random.choice(len(border_px), 500, replace=False) | |
| border_px = border_px[idx] | |
| r_mean = float(np.mean(border_px[:, 0])) | |
| g_mean = float(np.mean(border_px[:, 1])) | |
| b_mean = float(np.mean(border_px[:, 2])) | |
| # Color temperature | |
| warm = (r_mean + g_mean) / 2 | |
| cool = b_mean | |
| brightness = (r_mean + g_mean + b_mean) / 3 | |
| if warm > cool * 1.10: | |
| tags.add("warm lighting") | |
| elif cool > warm * 1.10: | |
| tags.add("cool lighting") | |
| # Brightness | |
| if brightness < 40: | |
| tags.add("dark atmosphere") | |
| elif brightness > 220: | |
| tags.add("bright") | |
| # Saturation (pastel vs vivid) | |
| r_std = float(np.std(border_px[:, 0])) | |
| g_std = float(np.std(border_px[:, 1])) | |
| b_std = float(np.std(border_px[:, 2])) | |
| sat = (r_std + g_std + b_std) / 3 | |
| if sat > 60: | |
| tags.add("vibrant colors") | |
| elif sat < 20: | |
| tags.add("pastel colors") | |
| # Indoor/outdoor heuristic | |
| border_std = float(np.mean(np.std(border_px, axis=0))) | |
| if border_std < 30: | |
| # Uniform border → likely sky (outdoors) | |
| if brightness > 150: | |
| tags.add("outdoors") | |
| elif border_std > 50: | |
| tags.add("indoors") | |
| return tags | |