Spaces:
Build error
Build error
| # -*- coding: utf-8 -*- | |
| """AdAnalyst_Mar30_logo | |
| #0. Install Libraries (Restart runtime at the end of the download and start running from step1) | |
| """ | |
| """#1. Import Libraries""" | |
| # Download the Dutch language corpus for textblob-nl | |
| import nltk | |
| nltk.download('alpino') | |
| import gradio as gr | |
| import torch | |
| import torchvision.transforms as transforms | |
| from PIL import Image, ImageDraw | |
| import timm | |
| import numpy as np | |
| import cv2 | |
| import pandas as pd | |
| import re | |
| from textblob import TextBlob | |
| from textblob_nl import PatternTagger, PatternAnalyzer | |
| from sklearn.cluster import KMeans | |
| import math | |
| from ultralytics import YOLO | |
| import easyocr | |
| import sys | |
| import os | |
| import zipfile | |
| from deepface import DeepFace | |
| """#2. Dictionaries""" | |
| # Define regex pattern for URLs | |
| url_pattern = re.compile( | |
| r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|' | |
| r'(?:%[0-9a-fA-F][0-9a-fA-F]))+|www\.(\w+\.)+\w+|(\w+\.)+(nl|com))' | |
| ) | |
| # Define lists for price indications, promotions, calls to action, and car brands | |
| price_indications = [r'\u20AC', '€', 'EUR', 'euro', 'euros'] | |
| promotion_words = ['korting', 'aanbieding', 'uitverkoop', 'prijsverlaging', 'afprijzing', 'voordeel', | |
| 'prijsbewust', 'goedkoop', 'besparing', 'tegen een lage prijs', 'speciale aanbieding', | |
| 'prijsvermindering', 'kortingbon', 'promotiecode', 'actie', 'actieprijs', 'tijdelijke aanbieding', | |
| 'nu met korting', 'extra voordelig', 'beste deal', 'flash sale', 'superaanbieding', 'seizoensuitverkoop', | |
| 'nu extra voordelig', 'nu met voordeel', 'exclusieve korting', 'laatste kans', 'tweede gratis', 'koopje', | |
| 'knalprijs', 'megakorting', 'laagste prijs'] | |
| call_to_action_phrases = ['bezoek', 'ga naar', 'dealer', 'showroom', 'garage', 'proefrit', 'testrit', | |
| 'probeer', 'bestel nu', 'koop nu', 'reserveren', 'vraag een offerte aan', 'configureren', | |
| 'ontdek meer', 'bekijk de aanbieding', 'registreer voor updates', 'neem contact op voor meer informatie', | |
| 'kom langs', 'start hier', 'doe een aanbetaling', 'ontvang een brochure', 'financieringsmogelijkheden', | |
| 'vraag om een demo', 'bestel vandaag nog', 'nu kopen', 'nu reserveren', 'meld je aan', 'schrijf je in', | |
| 'maak een afspraak', 'bel ons', 'neem contact op', 'plan je testrit', 'configureer je auto', 'klik hier', | |
| 'vraag een proefrit aan', 'vraag een offerte', 'doe mee', 'check het aanbod', 'registreer nu', 'nu ontdekken', | |
| 'bekijk onze modellen', 'bezoek onze website', 'vraag informatie aan', 'aanbetaling', 'aanvraag', 'aanvragen', 'afspraak', | |
| 'bekijk', 'bel', 'bestel', 'contact', 'configureer', 'demo', 'doe', 'download', 'financieringsmogelijkheden', 'info', | |
| 'informatie', 'kom', 'koop', 'laat je gegevens achter', 'maak', 'meld', 'neem', 'nu', 'offerte', 'ontdek', 'ontvang', | |
| 'plan', 'probeer', 'registreer', 'reserveren', 'schrijf', 'start', 'vraag', 'website'] | |
| # Expanded list of major car brands | |
| car_brands = [ | |
| 'ARCFOX', 'Acura', 'Aion', 'Alfa Romeo', 'Apollo', 'Artega', 'Aston Martin', 'Audi', | |
| 'BAC', 'BAIC', 'BMW', 'BYD', 'Baojun', 'Beijing', 'Bentley', 'Bestune', 'Bugatti', | |
| 'Buick', 'Cadillac', 'Caterham', 'Changan', 'Chery', 'Chevrolet', 'Chrysler', 'Citroën', | |
| 'Cupra', 'Daewoo', 'Dacia', 'Dodge', 'Dongfeng', 'DS Automobiles', 'Ferrari', 'Fiat', | |
| 'Fisker', 'Ford', 'GAC', 'GAZ', 'GMC', 'Geely', 'Genesis', 'Great Wall', 'Gumpert', | |
| 'Haval', 'Holden', 'Honda', 'Hongqi', 'Hozon Auto', 'Hummer', 'Hyundai', 'Infiniti', | |
| 'Isuzu', 'JAC', 'Jaguar', 'Jeep', 'Kia', 'Koenigsegg', 'LEVC', 'LINCOLN', 'Lamborghini', | |
| 'Land Rover', 'Leapmotor', 'Lexus', 'Li Auto', 'Lincoln', 'Lucid', 'Luxgen', 'Lynk & Co', | |
| 'MG', 'MINI', 'Mahindra', 'Maserati', 'Mazda', 'McLaren', 'Mercedes', 'Mercury', | |
| 'Mini', 'Mitsubishi', 'Morgan', 'NIO', 'Nissan', 'Noble', 'Oldsmobile', 'Opel', | |
| 'ORA', 'Pagani', 'Peugeot', 'Perodua', 'Polestar', 'Pontiac', 'Porsche', 'Proton', | |
| 'Ram', 'Reno', 'Rezvani', 'Rimac', 'Rivian', 'Rolls-Royce', 'Roewe', 'Saab', 'Saturn', | |
| 'SAIC', 'SEAT', 'SSC North America', 'Skoda', 'Smart', 'Spyker', 'SsangYong', | |
| 'Subaru', 'Suzuki', 'Tank', 'Tata', 'Tesla', 'Toyota', 'Trumpchi', 'VinFast', 'Volkswagen', | |
| 'Volvo', 'WEY', 'W Motors', 'Wiesmann', 'Xpeng', 'Zeekr' | |
| ] | |
| # COCO class names | |
| coco_classes = { | |
| 0: "person", | |
| 1: "bicycle", | |
| 2: "car", | |
| 3: "motorcycle", | |
| 4: "airplane", | |
| 5: "bus", | |
| 6: "train", | |
| 7: "truck", | |
| 8: "boat", | |
| 9: "traffic light", | |
| 10: "fire hydrant", | |
| 11: "stop sign", | |
| 12: "parking meter", | |
| 13: "bench", | |
| 14: "bird", | |
| 15: "cat", | |
| 16: "dog", | |
| 17: "horse", | |
| 18: "sheep", | |
| 19: "cow", | |
| 20: "elephant", | |
| 21: "bear", | |
| 22: "zebra", | |
| 23: "giraffe", | |
| 24: "backpack", | |
| 25: "umbrella", | |
| 26: "handbag", | |
| 27: "tie", | |
| 28: "suitcase", | |
| 29: "frisbee", | |
| 30: "skis", | |
| 31: "snowboard", | |
| 32: "sports ball", | |
| 33: "kite", | |
| 34: "baseball bat", | |
| 35: "baseball glove", | |
| 36: "skateboard", | |
| 37: "surfboard", | |
| 38: "tennis racket", | |
| 39: "bottle", | |
| 40: "wine glass", | |
| 41: "cup", | |
| 42: "fork", | |
| 43: "knife", | |
| 44: "spoon", | |
| 45: "bowl", | |
| 46: "banana", | |
| 47: "apple", | |
| 48: "sandwich", | |
| 49: "orange", | |
| 50: "broccoli", | |
| 51: "carrot", | |
| 52: "hot dog", | |
| 53: "pizza", | |
| 54: "donut", | |
| 55: "cake", | |
| 56: "chair", | |
| 57: "couch", | |
| 58: "potted plant", | |
| 59: "bed", | |
| 60: "dining table", | |
| 61: 'toilet', | |
| 62: 'tv', | |
| 63: 'laptop', | |
| 64: 'mouse', | |
| 65: 'remote', | |
| 66: 'keyboard', | |
| 67: 'cell phone', | |
| 68: 'microwave', | |
| 69: 'oven', | |
| 70: 'toaster', | |
| 71: 'sink', | |
| 72: 'refrigerator', | |
| 73: 'book', | |
| 74: 'clock', | |
| 75: 'vase', | |
| 76: 'scissors', | |
| 77: 'teddy bear', | |
| 78: 'hair drier', | |
| 79: 'toothbrush' | |
| } | |
| # Object categories as per your request | |
| animal_classes = [14, 15, 16, 17, 18, 19, 20, 21, 22, 23] | |
| transportation_classes = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] | |
| sports_classes = [29, 30, 31, 32, 33, 34, 35, 36, 37, 38] | |
| food_classes = [39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 60, 68, 69, 70, 71, 72] | |
| """#3. Function Modules | |
| ## Uniqueness and Consistency | |
| """ | |
| # 1. Compute image embedding | |
| # Load the pre-trained ViT model | |
| def load_vit_model(): | |
| model = timm.create_model('vit_base_patch16_224', pretrained=True, num_classes=0) | |
| model.eval() | |
| return model | |
| vit_model = load_vit_model() | |
| # Define image preprocessing steps | |
| def get_preprocess_transforms(): | |
| return transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.CenterCrop(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize( | |
| mean=(0.5, 0.5, 0.5), | |
| std=(0.5, 0.5, 0.5) | |
| ) | |
| ]) | |
| preprocess = get_preprocess_transforms() | |
| def compute_image_embedding(image, model, preprocess): | |
| input_tensor = preprocess(image).unsqueeze(0) | |
| with torch.no_grad(): | |
| embedding = model(input_tensor) | |
| return embedding.squeeze() | |
| # 2. Get embeddings from files | |
| def get_embeddings_from_files(file_list, model, preprocess): | |
| embeddings = [] | |
| for file_obj in file_list: | |
| image = Image.open(file_obj.name).convert('RGB') | |
| embedding = compute_image_embedding(image, model, preprocess) | |
| embeddings.append(embedding) | |
| return embeddings | |
| # 3. Calculate similarity scores | |
| def calculate_similarity_scores(focus_embedding, embeddings_list): | |
| from torch.nn.functional import cosine_similarity | |
| similarities = [] | |
| for emb in embeddings_list: | |
| sim = cosine_similarity(focus_embedding.unsqueeze(0), emb.unsqueeze(0)).item() | |
| similarities.append(sim) | |
| average_score = sum(similarities) / len(similarities) if similarities else None | |
| return round(average_score, 4) if average_score is not None else 'None' | |
| """## OCR""" | |
| # Extract textual features | |
| # load EasyOCTR model | |
| reader = easyocr.Reader(['nl','en'], gpu=False) | |
| def extract_textual_features(image): | |
| width, height = image.size | |
| image_area = width * height | |
| image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
| #reader = easyocr.Reader(['nl', 'en'], gpu=False) | |
| results = reader.readtext(image_cv) | |
| extracted_text_list = [] | |
| total_text_area = 0 | |
| mask = np.zeros((height, width), dtype=np.uint8) | |
| for (bbox, text, prob) in results: | |
| if text.strip(): | |
| extracted_text_list.append(text) | |
| points = np.array(bbox, dtype=np.int32) | |
| polygon = [] | |
| for point in points: | |
| polygon.append((int(point[0]), int(point[1]))) | |
| temp_mask = Image.new('L', (width, height), 0) | |
| ImageDraw.Draw(temp_mask).polygon(polygon, outline=1, fill=1) | |
| temp_array = np.array(temp_mask) | |
| mask = np.logical_or(mask, temp_array).astype(np.uint8) * 255 | |
| extracted_text = ' '.join(extracted_text_list).strip() | |
| num_char = len(extracted_text.replace(" ", "")) | |
| text_area = np.sum(mask > 0) | |
| text_area_ratio = text_area / image_area*100 if image_area > 0 else 0 | |
| text_area_ratio = round(text_area_ratio, 4) | |
| return extracted_text, num_char, text_area_ratio | |
| # 5. Perform sentiment analysis | |
| def perform_sentiment_analysis(text): | |
| if text.strip(): | |
| blob = TextBlob(text, pos_tagger=PatternTagger(), analyzer=PatternAnalyzer()) | |
| sentiment = blob.sentiment | |
| sentiment_polarity = round(sentiment[0], 4) | |
| sentiment_subjectivity = round(sentiment[1], 4) | |
| else: | |
| sentiment_polarity = 0.0 | |
| sentiment_subjectivity = 0.0 | |
| return sentiment_polarity, sentiment_subjectivity | |
| # 6. Analyze additional textual features | |
| def analyze_additional_text_features(text): | |
| # URL Count | |
| urls = re.findall(url_pattern, text) | |
| url_count = len(urls) | |
| # Price Indication Count | |
| price_count = sum(text.lower().count(word.lower()) for word in price_indications) | |
| # Promotion Indication Count | |
| promotion_count = sum(text.lower().count(word.lower()) for word in promotion_words) | |
| # Call to Action Count | |
| call_to_action_count = sum(text.lower().count(phrase.lower()) for phrase in call_to_action_phrases) | |
| # Brand Salience Count | |
| brand_count = sum(text.lower().count(brand.lower()) for brand in car_brands) | |
| return url_count, price_count, promotion_count, call_to_action_count, brand_count | |
| """## Basci Visual Features""" | |
| # Function to convert RGB to Hex | |
| def rgb_to_hex(color): | |
| return '#%02x%02x%02x' % tuple(color) | |
| # Get dominant color | |
| def get_dominant_color(image, k=4): | |
| # Resize image to reduce computation time | |
| image = image.resize((150, 150)) | |
| # Convert image to numpy array | |
| np_image = np.array(image) | |
| np_image = np_image.reshape((np_image.shape[0]*np_image.shape[1], 3)) | |
| # Use KMeans clustering | |
| kmeans = KMeans(n_clusters=k) | |
| kmeans.fit(np_image) | |
| # Get the cluster centers | |
| colors = kmeans.cluster_centers_ | |
| # Get counts of pixels in each cluster | |
| labels, counts = np.unique(kmeans.labels_, return_counts=True) | |
| # Find the most frequent color | |
| dominant_color = colors[np.argmax(counts)] | |
| # Convert to int and to hex | |
| dominant_color = dominant_color.astype(int) | |
| dominant_color_hex = rgb_to_hex(dominant_color) | |
| return dominant_color_hex, tuple(dominant_color) | |
| # Extract visual features | |
| def extract_visual_features(image): | |
| # Image resolution | |
| width, height = image.size | |
| # Average RGB values | |
| np_image = np.array(image) | |
| avg_r = np.mean(np_image[:, :, 0]) | |
| avg_g = np.mean(np_image[:, :, 1]) | |
| avg_b = np.mean(np_image[:, :, 2]) | |
| # Dominant color | |
| dominant_color_hex, dominant_color_rgb = get_dominant_color(image) | |
| # Warm/cold hue | |
| # Convert image to HSV | |
| hsv_image = image.convert('HSV') | |
| np_hsv = np.array(hsv_image) | |
| avg_hue = np.mean(np_hsv[:, :, 0]) | |
| # Convert hue from 0-255 to degrees | |
| avg_hue_deg = avg_hue * 360 / 255 | |
| # Determine warm or cold | |
| # Warm colors are from 0-60 and 300-360 degrees | |
| if (0 <= avg_hue_deg <= 60) or (300 <= avg_hue_deg <= 360): | |
| hue_category = 'Warm' | |
| else: | |
| hue_category = 'Cool' | |
| # Visual complexity (Shannon entropy) | |
| # Convert image to grayscale | |
| gray_image = image.convert('L') | |
| histogram = gray_image.histogram() | |
| histogram_length = sum(histogram) | |
| samples_probability = [float(h) / histogram_length for h in histogram if h != 0] | |
| entropy = -sum([p * math.log(p, 2) for p in samples_probability]) | |
| entropy = round(entropy / 8, 4) | |
| # Return the computed features | |
| return { | |
| 'Resolution': f'{width}x{height}', | |
| 'Dominant Color': dominant_color_hex, | |
| 'Dominant Color RGB': dominant_color_rgb, | |
| 'Hue Category': hue_category, | |
| 'Average Red': round(avg_r, 2), | |
| 'Average Green': round(avg_g, 2), | |
| 'Average Blue': round(avg_b, 2), | |
| 'Visual Complexity': round(entropy, 4) | |
| } | |
| """## YOLO: Perform object detection""" | |
| # Load YOLO model | |
| def load_yolo_model(): | |
| model = YOLO('yolo11s.pt') # Using YOLO medium model | |
| return model | |
| yolo_model = load_yolo_model() | |
| # QR code | |
| detector = cv2.QRCodeDetector() | |
| def perform_object_detection(image_pil): | |
| img_width, img_height = image_pil.size | |
| image_area = img_width * img_height | |
| np_image = np.array(image_pil) | |
| np_image_bgr = cv2.cvtColor(np_image, cv2.COLOR_RGB2BGR) | |
| retval, decoded_info, points, _ = detector.detectAndDecodeMulti(np_image_bgr) | |
| qr_code_count = len(decoded_info) | |
| # Run YOLO model | |
| results = yolo_model(np_image_bgr) | |
| detections = results[0] | |
| boxes = detections.boxes # Bounding boxes | |
| class_ids = boxes.cls.cpu().numpy().astype(int) | |
| confidences = boxes.conf.cpu().numpy() | |
| xyxy = boxes.xyxy.cpu().numpy() # Bounding box coordinates | |
| # Initialize counts and areas | |
| car_count = 0 | |
| car_coverage_area = 0 | |
| car_positions = [] | |
| person_count = 0 | |
| animal_count = 0 | |
| transportation_count = 0 | |
| sports_item_count = 0 | |
| food_item_count = 0 | |
| for cls_id, conf, bbox in zip(class_ids, confidences, xyxy): | |
| class_name = coco_classes.get(cls_id, 'Unknown') | |
| bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) | |
| if cls_id == 2: # Car class | |
| car_count += 1 | |
| car_coverage_area += bbox_area | |
| # Determine position | |
| center_x = (bbox[0] + bbox[2]) / 2 | |
| center_y = (bbox[1] + bbox[3]) / 2 | |
| if center_x < 2 * img_width / 5: | |
| horiz = 'Left' | |
| elif center_x > 3 * img_width / 5: | |
| horiz = 'Right' | |
| else: | |
| horiz = 'Center' | |
| if center_y < 2 * img_height / 5: | |
| vert = 'Top' | |
| elif center_y > 3 * img_height / 5: | |
| vert = 'Bottom' | |
| else: | |
| vert = 'Middle' | |
| car_positions.append(f"{vert}-{horiz}") | |
| elif cls_id == 0: | |
| person_count += 1 | |
| elif cls_id in animal_classes: | |
| animal_count += 1 | |
| elif cls_id in transportation_classes: | |
| transportation_count += 1 | |
| elif cls_id in sports_classes: | |
| sports_item_count += 1 | |
| elif cls_id in food_classes: | |
| food_item_count += 1 | |
| # Calculate coverage area ratio | |
| car_coverage_ratio = car_coverage_area / image_area *100 if image_area > 0 else 0 | |
| car_coverage_ratio = round(car_coverage_ratio, 2) | |
| # Get unique positions | |
| unique_positions = list(set(car_positions)) | |
| return { | |
| 'Car Count': car_count, | |
| 'Car Coverage Ratio': car_coverage_ratio, | |
| 'Car Positions': ', '.join(unique_positions) if unique_positions else 'None', | |
| 'Person Count': person_count, | |
| 'Animal Object Count': animal_count, | |
| 'Transportation Object Count': transportation_count, | |
| 'Sports Item Count': sports_item_count, | |
| 'Food Item Count': food_item_count, | |
| 'QR Code Count': qr_code_count | |
| } | |
| """## Logo""" | |
| # Commented out IPython magic to ensure Python compatibility. | |
| if not os.path.exists("yolov7"): | |
| with zipfile.ZipFile("yolov7.zip", 'r') as zip_ref: | |
| zip_ref.extractall(".") | |
| print("files", os.listdir(".")) | |
| sys.path.append("./yolov7") | |
| logo_model = torch.hub.load('./yolov7', 'custom', './logo_detection.pt', source='local') | |
| logo_model.conf = 0.25 | |
| def detect_logos(pil_image): | |
| image_np = np.array(pil_image) | |
| results = logo_model(image_np) | |
| # results.xyxy[0] [x1, y1, x2, y2, conf, cls] | |
| detections = results.xyxy[0].cpu().numpy() if results.xyxy[0] is not None else np.empty((0, 6)) | |
| logo_count = detections.shape[0] | |
| total_logo_area = 0 | |
| positions = [] | |
| img_height, img_width = image_np.shape[0], image_np.shape[1] | |
| image_area = img_height * img_width | |
| for det in detections: | |
| x1, y1, x2, y2, conf, cls = det | |
| box_area = (x2 - x1) * (y2 - y1) | |
| total_logo_area += box_area | |
| center_x = (x1 + x2) / 2 | |
| center_y = (y1 + y2) / 2 | |
| if center_x < img_width / 3: | |
| horiz = 'Left' | |
| elif center_x < 2 * img_width / 3: | |
| horiz = 'Center' | |
| else: | |
| horiz = 'Right' | |
| if center_y < img_height / 3: | |
| vert = 'Top' | |
| elif center_y < 2 * img_height / 3: | |
| vert = 'Middle' | |
| else: | |
| vert = 'Bottom' | |
| positions.append(f"{vert}-{horiz}") | |
| logo_area_ratio = total_logo_area / image_area*100 if image_area > 0 else 0 | |
| logo_area_ratio = round(logo_area_ratio,2) | |
| unique_positions = list(dict.fromkeys(positions)) | |
| positions_str = ", ".join(unique_positions) | |
| return { | |
| 'Logo Count': logo_count, | |
| 'Logo Coverage Ratio': logo_area_ratio, | |
| 'Logo Positions': positions_str} | |
| def analyze_face_features(image): | |
| #GDPR -- exclude gender and race inference | |
| # BGR format for deepface | |
| image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
| results = DeepFace.analyze( | |
| img_path=image_cv, | |
| actions=['emotion'], | |
| detector_backend='mtcnn', | |
| enforce_detection=False | |
| ) | |
| # list format | |
| if not isinstance(results, list): | |
| results = [results] | |
| # return none if no face detected | |
| if not results: | |
| return { | |
| "angry": 0, | |
| "disgust": 0, | |
| "fear": 0, | |
| "happy": 0, | |
| "sad": 0, | |
| "surprise": 0, | |
| "neutral": 0} | |
| # If no faces, return zeros | |
| if not results: | |
| return { | |
| "angry": 0, | |
| "disgust": 0, | |
| "fear": 0, | |
| "happy": 0, | |
| "sad": 0, | |
| "surprise": 0, | |
| "neutral": 0} | |
| # initiation | |
| emotions_sum = { | |
| "angry": 0, | |
| "disgust": 0, | |
| "fear": 0, | |
| "happy": 0, | |
| "sad": 0, | |
| "surprise": 0, | |
| "neutral": 0 | |
| } | |
| # process faces | |
| for face in results: | |
| for emotion, value in face['emotion'].items(): | |
| emotion_lower = emotion.lower() | |
| if emotion_lower in emotions_sum: | |
| emotions_sum[emotion_lower] += value | |
| # average emtions | |
| num_faces = len(results) | |
| avg_emotions = {emotion: round(value / num_faces, 2) for emotion, value in emotions_sum.items()} | |
| result = { | |
| "angry": avg_emotions["angry"], | |
| "disgust": avg_emotions["disgust"], | |
| "fear": avg_emotions["fear"], | |
| "happy": avg_emotions["happy"], | |
| "sad": avg_emotions["sad"], | |
| "surprise": avg_emotions["surprise"], | |
| "neutral": avg_emotions["neutral"], | |
| } | |
| return result | |
| """# 4. Run all analysis""" | |
| # Process image and compute all features | |
| def process_image(focal_image, same_brand_files, competitive_brand_files): | |
| # Input validation | |
| if focal_image is None: | |
| return ["Please upload the focal ad."] + [""] * 23 # Adjusted for total outputs | |
| # Compute embeddings | |
| focus_embedding = compute_image_embedding(focal_image, vit_model, preprocess) | |
| # Calculate scores | |
| if same_brand_files: | |
| same_brand_embeddings = get_embeddings_from_files(same_brand_files, vit_model, preprocess) | |
| consistency_score = calculate_similarity_scores(focus_embedding, same_brand_embeddings) | |
| consistency_score = round(consistency_score, 4) | |
| else: | |
| consistency_score = 'None' | |
| if competitive_brand_files: | |
| competitive_brand_embeddings = get_embeddings_from_files(competitive_brand_files, vit_model, preprocess) | |
| uniqueness_score = 1- calculate_similarity_scores(focus_embedding, competitive_brand_embeddings) | |
| uniqueness_score = round(uniqueness_score, 4) | |
| else: | |
| uniqueness_score = 'None' | |
| # Calculate ad_elasticity | |
| if consistency_score != 'None' and uniqueness_score != 'None': | |
| ad_elasticity = round(0.021 + 0.097*consistency_score + 0.110*uniqueness_score, 4) | |
| else: | |
| ad_elasticity = 'None' # Handle missing values gracefully | |
| # Extract textual features | |
| extracted_text, num_char, text_area_ratio = extract_textual_features(focal_image) | |
| # Sentiment analysis | |
| sentiment_polarity, sentiment_subjectivity = perform_sentiment_analysis(extracted_text) | |
| # Analyze additional textual features | |
| url_count, price_count, promotion_count, call_to_action_count, brand_salience_count = analyze_additional_text_features(extracted_text) | |
| # Extract visual features | |
| visual_features = extract_visual_features(focal_image) | |
| # Unpack visual features | |
| resolution = visual_features['Resolution'] | |
| dominant_color = visual_features['Dominant Color'] | |
| dominant_color_rgb = visual_features['Dominant Color RGB'] | |
| hue_category = visual_features['Hue Category'] | |
| avg_r = visual_features['Average Red'] | |
| avg_g = visual_features['Average Green'] | |
| avg_b = visual_features['Average Blue'] | |
| visual_complexity = visual_features['Visual Complexity'] | |
| # Perform object detection | |
| object_detection_results = perform_object_detection(focal_image) | |
| # Unpack object detection results | |
| car_count = object_detection_results['Car Count'] | |
| car_coverage_ratio = object_detection_results['Car Coverage Ratio'] | |
| car_positions = object_detection_results['Car Positions'] | |
| person_count = object_detection_results['Person Count'] | |
| animal_count = object_detection_results['Animal Object Count'] | |
| transportation_count = object_detection_results['Transportation Object Count'] | |
| sports_item_count = object_detection_results['Sports Item Count'] | |
| food_item_count = object_detection_results['Food Item Count'] | |
| qr_code_count = object_detection_results['QR Code Count'] | |
| # Perform logo detection | |
| logo_detection_results = detect_logos(focal_image) | |
| # Unpack logo detection results | |
| logo_count = logo_detection_results['Logo Count'] | |
| logo_area_ratio = logo_detection_results['Logo Coverage Ratio'] | |
| logo_positions = logo_detection_results['Logo Positions'] | |
| #emtion | |
| emotion_results = analyze_face_features(focal_image) | |
| emo_angry = emotion_results["angry"] | |
| emo_disgust = emotion_results["disgust"] | |
| emo_fear = emotion_results["fear"] | |
| emo_happy = emotion_results["happy"] | |
| emo_sad = emotion_results["sad"] | |
| emo_surprise = emotion_results["surprise"] | |
| emo_neutral = emotion_results["neutral"] | |
| # Return all outputs | |
| return [ | |
| uniqueness_score, | |
| consistency_score, | |
| ad_elasticity, | |
| resolution, | |
| dominant_color, | |
| hue_category, | |
| avg_r, | |
| avg_g, | |
| avg_b, | |
| visual_complexity, | |
| car_count, | |
| car_coverage_ratio, | |
| car_positions, | |
| logo_count, | |
| logo_area_ratio, | |
| logo_positions, | |
| person_count, | |
| animal_count, | |
| transportation_count, | |
| sports_item_count, | |
| food_item_count, | |
| qr_code_count, | |
| num_char, | |
| text_area_ratio, | |
| sentiment_polarity, | |
| sentiment_subjectivity, | |
| url_count, | |
| price_count, | |
| promotion_count, | |
| call_to_action_count, | |
| brand_salience_count, | |
| emo_angry, | |
| emo_disgust, | |
| emo_fear, | |
| emo_happy, | |
| emo_sad, | |
| emo_surprise, | |
| emo_neutral | |
| ] | |
| import cv2, requests | |
| image = cv2.imdecode(np.frombuffer(requests.get("https://github.com/JaidedAI/EasyOCR/raw/master/examples/english.png").content, np.uint8), cv2.IMREAD_COLOR) | |
| image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) | |
| image = image.convert('RGB') | |
| image | |
| process_image(image, None, None) | |
| """#5. Gradio Interface""" | |
| def get_score_card(value, title, thresholds, colors, labels): | |
| try: | |
| value = float(value) | |
| position = 10 # default | |
| for i, th in enumerate(thresholds): | |
| if value < th: | |
| position = 10 + i * 20 | |
| break | |
| else: | |
| position = 90 | |
| except (ValueError, TypeError): | |
| return f"<div style='color:gray;'>{title}: N/A</div>" | |
| color_bars = "".join([f"<div style='width:{100/len(colors)}%; background-color:{c};'></div>" for c in colors]) | |
| return f""" | |
| <div style='border-radius:15px; padding:20px; background:#F9F9F9; box-shadow:0 4px 10px rgba(0,0,0,0.1);'> | |
| <div style='font-size:18px; font-weight:bold; margin-bottom:10px;'>{title}: {value:.2f}</div> | |
| <div style='position:relative; height:40px;'> | |
| <div style='display:flex; height:16px; border-radius:8px; overflow:hidden;'> | |
| {color_bars} | |
| </div> | |
| <div style='position:absolute; top:18px; left:{position}%; transform:translateX(-50%);'> | |
| <div style='width:0;height:0;border-left:8px solid transparent;border-right:8px solid transparent;border-top:12px solid black;'></div> | |
| </div> | |
| </div> | |
| <div style='display:flex; justify-content:space-between; margin-top:8px; font-size:14px;'> | |
| {"".join([f"<span>{l}</span>" for l in labels])} | |
| </div> | |
| </div> | |
| """ | |
| def get_consistency_card(value): | |
| return get_score_card( | |
| value=value, | |
| title="Within-Brand Ad Consistency", | |
| thresholds=[0.1736, 0.3003, 0.5538, 0.6806], # +- 1 or 2 SD | |
| colors=["#FF4C4C", "#FFA500", "#FFD700", "#90EE90", "#008000"], | |
| labels=["Poor", "Low", "Avg", "Good", "Exc"] | |
| ) | |
| def get_distinctiveness_card(value): | |
| return get_score_card( | |
| value=value, | |
| title="Cross-Brand Ad Uniqueness", | |
| thresholds=[0.3937, 0.5000, 0.7125, 0.8187], | |
| colors=["#FF4C4C", "#FFA500", "#FFD700", "#90EE90", "#008000"], | |
| labels=["Poor", "Low", "Avg", "Good", "Exc"] | |
| ) | |
| def get_elasticity_card(value): | |
| return get_score_card( | |
| value=value, | |
| title="Ad Elasticity", | |
| thresholds=[0.08, 0.12, 0.16, 0.20], | |
| colors=["#FF4C4C", "#FFA500", "#FFD700", "#90EE90", "#008000"], | |
| labels=["Poor", "Low", "Avg", "Good", "Exc"], | |
| ) | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Ad Analyst") | |
| # Input Components | |
| with gr.Row(): | |
| focal_ad = gr.Image(type='pil', label='Focal Ad', height=200) | |
| same_brand_ads = gr.File(file_types=['image'], label='Same Brand Ads', file_count='multiple') | |
| competitive_brand_ads = gr.File(file_types=['image'], label='Competitive Brand Ads', file_count='multiple') | |
| run_button = gr.Button('Run Analysis') | |
| # Output Components | |
| gr.Markdown("## Comprehensive Indexes") | |
| with gr.Row(): | |
| distinctiveness_score = gr.HTML(label='Cross-Brand Ad Uniqueness') | |
| consistency_score = gr.HTML(label='Within-Brand Ad Consistency') | |
| ad_elasticity = gr.HTML(label='Ad Elasticity') | |
| gr.Markdown("## Visual Features") | |
| with gr.Row(): | |
| resolution_output = gr.Textbox(label='Resolution') | |
| hue_category_output = gr.Textbox(label='Hue Category') | |
| dominant_color_output = gr.Textbox(label='Dominant Color') | |
| dominant_color_indicator = gr.ColorPicker(label='Color Indicator', value='#FFFFFF') | |
| with gr.Row(): | |
| avg_r_output = gr.Textbox(label='Average Red') | |
| avg_g_output = gr.Textbox(label='Average Green') | |
| avg_b_output = gr.Textbox(label='Average Blue') | |
| visual_complexity_output = gr.Textbox(label='Visual Complexity (0 to 1)') | |
| gr.Markdown("## Object Detection") | |
| with gr.Row(): | |
| car_count_output = gr.Textbox(label='Car Count') | |
| car_coverage_ratio_output = gr.Textbox(label='Car Coverage Ratio (%)') | |
| car_positions_output = gr.Textbox(label='Car Positions') | |
| with gr.Row(): | |
| logo_count_output = gr.Textbox(label='Logo Count') | |
| logo_coverage_ratio_output = gr.Textbox(label='Logo Coverage Ratio (%)') | |
| logo_positions_output = gr.Textbox(label='Logo Positions') | |
| with gr.Row(): | |
| person_count_output = gr.Textbox(label='Person Count') | |
| animal_count_output = gr.Textbox(label='Animal Count') | |
| transportation_count_output = gr.Textbox(label='Other Transportation Object Count') | |
| with gr.Row(): | |
| sports_item_count_output = gr.Textbox(label='Sports Equipment Count') | |
| food_item_count_output = gr.Textbox(label='Food and Dining Item Count') | |
| qr_code_count_output = gr.Textbox(label='QR Code Count') # QR code count output | |
| gr.Markdown("## Textual Features") | |
| with gr.Row(): | |
| num_char = gr.Textbox(label='Character Count') | |
| text_area_ratio = gr.Textbox(label='Text Area Ratio (%)') | |
| sentiment_polarity_output = gr.Textbox(label='Sentiment Polarity (-1 to 1)') | |
| with gr.Row(): | |
| subjectivity_output = gr.Textbox(label='Subjectivity (0 to 1)') | |
| url_count_output = gr.Textbox(label='URL Count') | |
| price_indication_count_output = gr.Textbox(label='Price Indication Count') | |
| with gr.Row(): | |
| promotion_indication_count_output = gr.Textbox(label='Promotion Indication Count') | |
| call_to_action_count_output = gr.Textbox(label='Call-to-Action Count') | |
| brand_salience_output = gr.Textbox(label='Brand Salience') | |
| gr.Markdown("## Facial Emotions") | |
| with gr.Row(): | |
| emo_angry_output = gr.Textbox(label='Angry') | |
| emo_disgust_output = gr.Textbox(label='Disgust') | |
| emo_fear_output = gr.Textbox(label='Fear') | |
| with gr.Row(): | |
| emo_happy_output = gr.Textbox(label='Happy') | |
| emo_sad_output = gr.Textbox(label='Sad') | |
| emo_surprise_output = gr.Textbox(label='Surprise') | |
| emo_neutral_output = gr.Textbox(label='Neutral') | |
| # Define the function to be called when the button is clicked | |
| def process_and_display(focal_image, same_brand_files, competitive_brand_files): | |
| # Call the process_image function and get the outputs | |
| outputs = process_image(focal_image, same_brand_files, competitive_brand_files) | |
| # Set the dominant color indicator | |
| dominant_color_hex = outputs[4] | |
| dominant_color_indicator = dominant_color_hex if dominant_color_hex else '#FFFFFF' | |
| outputs.insert(5, dominant_color_indicator) # Insert after dominant color output | |
| ad_elasticity_value = outputs[2] | |
| ad_elasticity_card_html = get_elasticity_card(ad_elasticity_value) | |
| outputs[2]=ad_elasticity_card_html | |
| consistency_score_value = outputs[1] | |
| consistency_score_card_html = get_consistency_card(consistency_score_value) | |
| outputs[1]=consistency_score_card_html | |
| distinctiveness_score_value = outputs[0] | |
| distinctiveness_score_card_html = get_distinctiveness_card(distinctiveness_score_value) | |
| outputs[0]=distinctiveness_score_card_html | |
| return outputs | |
| # Set up the event handler | |
| run_button.click( | |
| fn=process_and_display, | |
| inputs=[focal_ad, same_brand_ads, competitive_brand_ads], | |
| outputs=[ | |
| distinctiveness_score, | |
| consistency_score, | |
| ad_elasticity, | |
| resolution_output, | |
| dominant_color_output, | |
| dominant_color_indicator, | |
| hue_category_output, | |
| avg_r_output, | |
| avg_g_output, | |
| avg_b_output, | |
| visual_complexity_output, | |
| car_count_output, | |
| car_coverage_ratio_output, | |
| car_positions_output, | |
| logo_count_output, | |
| logo_coverage_ratio_output, | |
| logo_positions_output, | |
| person_count_output, | |
| animal_count_output, | |
| transportation_count_output, | |
| sports_item_count_output, | |
| food_item_count_output, | |
| qr_code_count_output, | |
| num_char, | |
| text_area_ratio, | |
| sentiment_polarity_output, | |
| subjectivity_output, | |
| url_count_output, | |
| price_indication_count_output, | |
| promotion_indication_count_output, | |
| call_to_action_count_output, | |
| brand_salience_output, | |
| emo_angry_output, | |
| emo_disgust_output, | |
| emo_fear_output, | |
| emo_happy_output, | |
| emo_sad_output, | |
| emo_surprise_output, | |
| emo_neutral_output | |
| ] | |
| ) | |
| # Launch the app | |
| demo.launch() |