from __future__ import annotations import re MAX_WORDS = 12 SCENE_PATTERNS = [ ("variation", r"\bvari(?:ation|ations|ant|ants|ous)\b"), ("set of", r"\bsets?\s+of\b"), ("collection", r"\bcollections?\b"), ("assorted", r"\bassorted\b"), ("multiple", r"\bmultiple\b"), ("pair of", r"\bpairs?\s+of\b"), ("group of", r"\bgroups?\s+of\b"), ("scene", r"\bscenes?\b"), ("elements", r"\belements?\b"), ("featuring", r"\bfeaturing\b"), ("different", r"\bdifferent\b"), ] MODIFIERS = { "black", "white", "red", "blue", "green", "yellow", "orange", "purple", "pink", "brown", "gray", "grey", "gold", "golden", "silver", "bronze", "copper", "beige", "tan", "teal", "cyan", "magenta", "maroon", "navy", "ivory", "cream", "turquoise", "lavender", "olive", "crimson", "scarlet", "dark", "light", "pale", "bright", "deep", "pastel", "neon", "striped", "wooden", "wood", "metal", "metallic", "plastic", "stone", "glass", "steel", "iron", "brick", "concrete", "leather", "fabric", "marble", } _WORD = re.compile(r"[a-z][a-z'-]*") _AND = re.compile(r"\band\b") def _joins_noun_phrases(text): parts = _AND.split(text) if len(parts) < 2: return False for left, right in zip(parts[:-1], parts[1:]): if "," in left: return True lw = _WORD.findall(left) rw = _WORD.findall(right) if not lw or not rw: return True if lw[-1] in MODIFIERS and rw[0] in MODIFIERS: continue return True return False def reject_reason(caption): if not caption or not caption.strip(): return "empty" text = caption.lower() if len(caption.split()) > MAX_WORDS: return "too long" for name, pattern in SCENE_PATTERNS: if re.search(pattern, text): return name if _joins_noun_phrases(text): return "and" return None def is_single_object(caption): return reject_reason(caption) is None