| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090 |
- # # ==================== visual_processing_service.py (FIXED - Dynamic Detection) ====================
- # import torch
- # import cv2
- # import numpy as np
- # import requests
- # from io import BytesIO
- # from PIL import Image
- # from typing import Dict, List, Optional, Tuple
- # import logging
- # from transformers import CLIPProcessor, CLIPModel
- # from sklearn.cluster import KMeans
- # logger = logging.getLogger(__name__)
- # class VisualProcessingService:
- # """Service for extracting visual attributes from product images using CLIP."""
-
- # # Class-level caching (shared across instances)
- # _clip_model = None
- # _clip_processor = None
- # _device = None
-
- # # Define category-specific attributes
- # CATEGORY_ATTRIBUTES = {
- # "clothing": {
- # "products": ["t-shirt", "shirt", "dress", "pants", "jeans", "shorts",
- # "skirt", "jacket", "coat", "sweater", "hoodie", "top", "blouse"],
- # "attributes": {
- # "pattern": ["solid color", "striped", "checkered", "graphic print", "floral", "geometric", "plain"],
- # "material": ["cotton", "polyester", "denim", "leather", "silk", "wool", "linen", "blend"],
- # "style": ["casual", "formal", "sporty", "streetwear", "elegant", "vintage", "bohemian"],
- # "fit": ["slim fit", "regular fit", "loose fit", "oversized", "tailored"],
- # "neckline": ["crew neck", "v-neck", "round neck", "collar", "scoop neck"],
- # "sleeve_type": ["short sleeve", "long sleeve", "sleeveless", "3/4 sleeve"],
- # "closure_type": ["button", "zipper", "pull-on", "snap", "tie"]
- # }
- # },
- # "tools": {
- # "products": ["screwdriver", "hammer", "wrench", "pliers", "drill", "saw",
- # "measuring tape", "level", "chisel", "file"],
- # "attributes": {
- # "material": ["steel", "aluminum", "plastic", "wood", "rubber", "chrome"],
- # "type": ["manual", "electric", "pneumatic", "cordless", "corded"],
- # "finish": ["chrome plated", "powder coated", "stainless steel", "painted"],
- # "handle_type": ["rubber grip", "plastic", "wooden", "cushioned", "ergonomic"]
- # }
- # },
- # "electronics": {
- # "products": ["phone", "laptop", "tablet", "headphones", "speaker", "camera",
- # "smartwatch", "charger", "mouse", "keyboard"],
- # "attributes": {
- # "material": ["plastic", "metal", "glass", "aluminum", "rubber"],
- # "style": ["modern", "minimalist", "sleek", "industrial", "vintage"],
- # "finish": ["matte", "glossy", "metallic", "textured"],
- # "connectivity": ["wireless", "wired", "bluetooth", "USB"]
- # }
- # },
- # "furniture": {
- # "products": ["chair", "table", "sofa", "bed", "desk", "shelf", "cabinet",
- # "dresser", "bench", "stool"],
- # "attributes": {
- # "material": ["wood", "metal", "glass", "plastic", "fabric", "leather"],
- # "style": ["modern", "traditional", "industrial", "rustic", "contemporary", "vintage"],
- # "finish": ["natural wood", "painted", "stained", "laminated", "upholstered"]
- # }
- # },
- # "home_decor": {
- # "products": ["painting", "canvas", "wall art", "frame", "vase", "lamp",
- # "mirror", "clock", "sculpture", "poster"],
- # "attributes": {
- # "style": ["modern", "abstract", "traditional", "contemporary", "vintage", "minimalist"],
- # "material": ["canvas", "wood", "metal", "glass", "ceramic", "paper"],
- # "finish": ["glossy", "matte", "textured", "framed", "gallery wrapped"],
- # "theme": ["nature", "geometric", "floral", "landscape", "portrait", "abstract"]
- # }
- # },
- # "kitchen": {
- # "products": ["pot", "pan", "knife", "utensil", "plate", "bowl", "cup",
- # "appliance", "cutting board", "container"],
- # "attributes": {
- # "material": ["stainless steel", "aluminum", "ceramic", "glass", "plastic", "wood"],
- # "finish": ["non-stick", "stainless", "enameled", "anodized"],
- # "type": ["manual", "electric", "dishwasher safe"]
- # }
- # }
- # }
-
- # def __init__(self):
- # pass
-
- # @classmethod
- # def _get_device(cls):
- # """Get optimal device."""
- # if cls._device is None:
- # cls._device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- # logger.info(f"Visual Processing using device: {cls._device}")
- # return cls._device
-
- # @classmethod
- # def _get_clip_model(cls):
- # """Lazy load CLIP model with class-level caching."""
- # if cls._clip_model is None:
- # logger.info("Loading CLIP model (this may take a few minutes on first use)...")
- # cls._clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
- # cls._clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
-
- # device = cls._get_device()
- # cls._clip_model.to(device)
- # cls._clip_model.eval()
-
- # logger.info("✓ CLIP model loaded successfully")
- # return cls._clip_model, cls._clip_processor
-
- # def download_image(self, image_url: str) -> Optional[Image.Image]:
- # """Download image from URL."""
- # try:
- # response = requests.get(image_url, timeout=10)
- # response.raise_for_status()
- # image = Image.open(BytesIO(response.content)).convert('RGB')
- # return image
- # except Exception as e:
- # logger.error(f"Error downloading image from {image_url}: {str(e)}")
- # return None
-
- # def extract_dominant_colors(self, image: Image.Image, n_colors: int = 3) -> List[Dict]:
- # """Extract dominant colors using K-means."""
- # try:
- # # Resize for faster processing
- # img_small = image.resize((150, 150))
- # img_array = np.array(img_small)
- # pixels = img_array.reshape(-1, 3)
-
- # # K-means clustering
- # kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=5)
- # kmeans.fit(pixels)
-
- # colors = []
- # labels_counts = np.bincount(kmeans.labels_)
-
- # for i, center in enumerate(kmeans.cluster_centers_):
- # rgb = tuple(center.astype(int))
- # color_name = self._get_color_name_simple(rgb)
- # percentage = float(labels_counts[i] / len(kmeans.labels_) * 100)
-
- # colors.append({
- # "name": color_name,
- # "rgb": rgb,
- # "percentage": percentage
- # })
-
- # colors.sort(key=lambda x: x['percentage'], reverse=True)
- # return colors
-
- # except Exception as e:
- # logger.error(f"Error extracting colors: {str(e)}")
- # return []
-
- # def _get_color_name_simple(self, rgb: Tuple[int, int, int]) -> str:
- # """
- # Simple color name detection without webcolors dependency.
- # Maps RGB to basic color names.
- # """
- # r, g, b = rgb
-
- # # Define basic color ranges
- # colors = {
- # 'black': (r < 50 and g < 50 and b < 50),
- # 'white': (r > 200 and g > 200 and b > 200),
- # 'gray': (abs(r - g) < 30 and abs(g - b) < 30 and abs(r - b) < 30 and 50 <= r <= 200),
- # 'red': (r > 150 and g < 100 and b < 100),
- # 'green': (g > 150 and r < 100 and b < 100),
- # 'blue': (b > 150 and r < 100 and g < 100),
- # 'yellow': (r > 200 and g > 200 and b < 100),
- # 'orange': (r > 200 and 100 < g < 200 and b < 100),
- # 'purple': (r > 100 and b > 100 and g < 100),
- # 'pink': (r > 200 and 100 < g < 200 and 100 < b < 200),
- # 'brown': (50 < r < 150 and 30 < g < 100 and b < 80),
- # 'cyan': (r < 100 and g > 150 and b > 150),
- # }
-
- # for color_name, condition in colors.items():
- # if condition:
- # return color_name
-
- # # Default fallback
- # if r > g and r > b:
- # return 'red'
- # elif g > r and g > b:
- # return 'green'
- # elif b > r and b > g:
- # return 'blue'
- # else:
- # return 'gray'
-
- # def classify_with_clip(
- # self,
- # image: Image.Image,
- # candidates: List[str],
- # attribute_name: str,
- # confidence_threshold: float = 0.15
- # ) -> Dict:
- # """Use CLIP to classify image against candidate labels."""
- # try:
- # model, processor = self._get_clip_model()
- # device = self._get_device()
-
- # # Prepare inputs
- # inputs = processor(
- # text=candidates,
- # images=image,
- # return_tensors="pt",
- # padding=True
- # )
-
- # # Move to device
- # inputs = {k: v.to(device) for k, v in inputs.items()}
-
- # # Get predictions
- # with torch.no_grad():
- # outputs = model(**inputs)
- # logits_per_image = outputs.logits_per_image
- # probs = logits_per_image.softmax(dim=1).cpu()
-
- # # Get top predictions
- # top_k = min(3, len(candidates))
- # top_probs, top_indices = torch.topk(probs[0], k=top_k)
-
- # results = []
- # for prob, idx in zip(top_probs, top_indices):
- # if prob.item() > confidence_threshold:
- # results.append({
- # "value": candidates[idx.item()],
- # "confidence": float(prob.item())
- # })
-
- # return {
- # "attribute": attribute_name,
- # "predictions": results
- # }
-
- # except Exception as e:
- # logger.error(f"Error in CLIP classification for {attribute_name}: {str(e)}")
- # return {"attribute": attribute_name, "predictions": []}
-
- # def detect_product_category(self, image: Image.Image) -> Tuple[str, float]:
- # """
- # First detect which category the product belongs to.
- # Returns: (category_name, confidence)
- # """
- # # Get all product types from all categories
- # all_categories = []
- # category_map = {}
-
- # for category, data in self.CATEGORY_ATTRIBUTES.items():
- # for product in data["products"]:
- # all_categories.append(f"a photo of a {product}")
- # category_map[f"a photo of a {product}"] = category
-
- # # Classify
- # result = self.classify_with_clip(image, all_categories, "category_detection", confidence_threshold=0.10)
-
- # if result["predictions"]:
- # best_match = result["predictions"][0]
- # detected_category = category_map[best_match["value"]]
- # product_type = best_match["value"].replace("a photo of a ", "")
- # confidence = best_match["confidence"]
-
- # logger.info(f"Detected category: {detected_category}, product: {product_type}, confidence: {confidence:.3f}")
- # return detected_category, product_type, confidence
-
- # return "unknown", "unknown", 0.0
-
- # def process_image(
- # self,
- # image_url: str,
- # product_type_hint: Optional[str] = None
- # ) -> Dict:
- # """
- # Main method to process image and extract visual attributes.
- # Now dynamically detects product category first.
- # """
- # import time
- # start_time = time.time()
-
- # try:
- # # Download image
- # image = self.download_image(image_url)
- # if image is None:
- # return {
- # "visual_attributes": {},
- # "error": "Failed to download image"
- # }
-
- # visual_attributes = {}
- # detailed_predictions = {}
-
- # # Step 1: Detect product category
- # detected_category, detected_product_type, category_confidence = self.detect_product_category(image)
-
- # # If confidence is too low, return minimal info
- # if category_confidence < 0.10:
- # logger.warning(f"Low confidence in category detection ({category_confidence:.3f}). Returning basic attributes only.")
- # colors = self.extract_dominant_colors(image, n_colors=3)
- # if colors:
- # visual_attributes["primary_color"] = colors[0]["name"]
- # visual_attributes["color_palette"] = [c["name"] for c in colors]
-
- # return {
- # "visual_attributes": visual_attributes,
- # "category_confidence": category_confidence,
- # "processing_time": round(time.time() - start_time, 2)
- # }
-
- # # Add detected product type
- # visual_attributes["product_type"] = detected_product_type
- # visual_attributes["category"] = detected_category
-
- # # Step 2: Extract color (universal attribute)
- # colors = self.extract_dominant_colors(image, n_colors=3)
- # if colors:
- # visual_attributes["primary_color"] = colors[0]["name"]
- # visual_attributes["color_palette"] = [c["name"] for c in colors]
-
- # # Step 3: Extract category-specific attributes
- # if detected_category in self.CATEGORY_ATTRIBUTES:
- # category_config = self.CATEGORY_ATTRIBUTES[detected_category]
-
- # for attr_name, attr_values in category_config["attributes"].items():
- # # Use higher confidence threshold for category-specific attributes
- # result = self.classify_with_clip(image, attr_values, attr_name, confidence_threshold=0.20)
-
- # if result["predictions"]:
- # # Only add if confidence is reasonable
- # best_prediction = result["predictions"][0]
- # if best_prediction["confidence"] > 0.20:
- # visual_attributes[attr_name] = best_prediction["value"]
- # detailed_predictions[attr_name] = result
-
- # processing_time = time.time() - start_time
-
- # return {
- # "visual_attributes": visual_attributes,
- # "detailed_predictions": detailed_predictions,
- # "category_confidence": category_confidence,
- # "processing_time": round(processing_time, 2)
- # }
-
- # except Exception as e:
- # logger.error(f"Error processing image: {str(e)}")
- # return {
- # "visual_attributes": {},
- # "error": str(e),
- # "processing_time": round(time.time() - start_time, 2)
- # }
- # ==================== visual_processing_service.py (FIXED - Smart Subcategory Detection) ====================
- import torch
- import numpy as np
- import requests
- from io import BytesIO
- from PIL import Image
- from typing import Dict, List, Optional, Tuple
- import logging
- from transformers import CLIPProcessor, CLIPModel
- from sklearn.cluster import KMeans
- logger = logging.getLogger(__name__)
- import os
- os.environ['TOKENIZERS_PARALLELISM'] = 'false' # Disable tokenizer warnings
- import warnings
- warnings.filterwarnings('ignore') # Suppress all warnings
- class VisualProcessingService:
- """Service for extracting visual attributes from product images using CLIP with smart subcategory detection."""
-
- # Class-level caching (shared across instances)
- _clip_model = None
- _clip_processor = None
- _device = None
-
- # Define hierarchical category structure with subcategories
- CATEGORY_ATTRIBUTES = {
- "clothing": {
- "subcategories": {
- "tops": {
- "products": ["t-shirt", "shirt", "blouse", "top", "sweater", "hoodie", "tank top", "polo shirt"],
- "attributes": {
- "pattern": ["solid color", "striped", "checkered", "graphic print", "floral", "geometric", "plain", "logo print"],
- "material": ["cotton", "polyester", "silk", "wool", "linen", "blend", "knit"],
- "style": ["casual", "formal", "sporty", "streetwear", "elegant", "vintage", "minimalist"],
- "fit": ["slim fit", "regular fit", "loose fit", "oversized", "fitted"],
- "neckline": ["crew neck", "v-neck", "round neck", "collar", "scoop neck", "henley"],
- "sleeve_type": ["short sleeve", "long sleeve", "sleeveless", "3/4 sleeve", "cap sleeve"],
- "closure_type": ["button-up", "zipper", "pull-on", "snap button"]
- }
- },
- "bottoms": {
- "products": ["jeans", "pants", "trousers", "shorts", "chinos", "cargo pants", "leggings"],
- "attributes": {
- "pattern": ["solid color", "distressed", "faded", "plain", "washed", "dark wash", "light wash"],
- "material": ["denim", "cotton", "polyester", "wool", "blend", "twill", "corduroy"],
- "style": ["casual", "formal", "sporty", "vintage", "modern", "workwear"],
- "fit": ["slim fit", "regular fit", "loose fit", "skinny", "bootcut", "straight leg", "relaxed fit"],
- "rise": ["high rise", "mid rise", "low rise"],
- "closure_type": ["button fly", "zipper fly", "elastic waist", "drawstring"],
- "length": ["full length", "cropped", "ankle length", "capri"]
- }
- },
- "dresses_skirts": {
- "products": ["dress", "skirt", "gown", "sundress", "maxi dress", "mini skirt"],
- "attributes": {
- "pattern": ["solid color", "floral", "striped", "geometric", "plain", "printed", "polka dot"],
- "material": ["cotton", "silk", "polyester", "linen", "blend", "chiffon", "satin"],
- "style": ["casual", "formal", "cocktail", "bohemian", "vintage", "elegant", "party"],
- "fit": ["fitted", "loose", "a-line", "bodycon", "flowy", "wrap"],
- "neckline": ["crew neck", "v-neck", "scoop neck", "halter", "off-shoulder", "sweetheart"],
- "sleeve_type": ["short sleeve", "long sleeve", "sleeveless", "3/4 sleeve", "flutter sleeve"],
- "length": ["mini", "midi", "maxi", "knee-length", "floor-length"]
- }
- },
- "outerwear": {
- "products": ["jacket", "coat", "blazer", "windbreaker", "parka", "bomber jacket", "denim jacket"],
- "attributes": {
- "pattern": ["solid color", "plain", "quilted", "textured"],
- "material": ["leather", "denim", "wool", "polyester", "cotton", "nylon", "fleece"],
- "style": ["casual", "formal", "sporty", "vintage", "military", "biker"],
- "fit": ["slim fit", "regular fit", "oversized", "cropped"],
- "closure_type": ["zipper", "button", "snap button", "toggle"],
- "length": ["cropped", "hip length", "thigh length", "knee length"]
- }
- }
- }
- },
- "footwear": {
- "products": ["sneakers", "boots", "sandals", "heels", "loafers", "flats", "slippers"],
- "attributes": {
- "material": ["leather", "canvas", "suede", "synthetic", "rubber", "mesh"],
- "style": ["casual", "formal", "athletic", "vintage", "modern"],
- "closure_type": ["lace-up", "slip-on", "velcro", "buckle", "zipper"],
- "toe_style": ["round toe", "pointed toe", "square toe", "open toe", "closed toe"]
- }
- },
- "tools": {
- "products": ["screwdriver", "hammer", "wrench", "pliers", "drill", "saw", "measuring tape"],
- "attributes": {
- "material": ["steel", "aluminum", "plastic", "rubber", "chrome", "iron"],
- "type": ["manual", "electric", "pneumatic", "cordless", "corded"],
- "finish": ["chrome plated", "powder coated", "stainless steel", "painted"],
- "handle_type": ["rubber grip", "plastic", "wooden", "ergonomic", "cushioned"]
- }
- },
- "electronics": {
- "products": ["phone", "laptop", "tablet", "headphones", "speaker", "camera", "smartwatch", "earbuds"],
- "attributes": {
- "material": ["plastic", "metal", "glass", "aluminum", "rubber", "silicone"],
- "style": ["modern", "minimalist", "sleek", "industrial", "vintage"],
- "finish": ["matte", "glossy", "metallic", "textured", "transparent"],
- "connectivity": ["wireless", "wired", "bluetooth", "USB-C", "USB"]
- }
- },
- "furniture": {
- "products": ["chair", "table", "sofa", "bed", "desk", "shelf", "cabinet", "bench"],
- "attributes": {
- "material": ["wood", "metal", "glass", "plastic", "fabric", "leather", "rattan"],
- "style": ["modern", "traditional", "industrial", "rustic", "contemporary", "vintage", "scandinavian"],
- "finish": ["natural wood", "painted", "stained", "laminated", "upholstered", "polished"]
- }
- }
- }
-
- def __init__(self):
- pass
-
- @classmethod
- def _get_device(cls):
- """Get optimal device."""
- if cls._device is None:
- cls._device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- logger.info(f"Visual Processing using device: {cls._device}")
- return cls._device
-
- @classmethod
- def _get_clip_model(cls):
- """Lazy load CLIP model with class-level caching."""
- if cls._clip_model is None:
- logger.info("Loading CLIP model (this may take a few minutes on first use)...")
- cls._clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
- cls._clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
-
- device = cls._get_device()
- cls._clip_model.to(device)
- cls._clip_model.eval()
-
- logger.info("✓ CLIP model loaded successfully")
- return cls._clip_model, cls._clip_processor
-
- def download_image(self, image_url: str) -> Optional[Image.Image]:
- """Download image from URL."""
- try:
- response = requests.get(image_url, timeout=10)
- response.raise_for_status()
- image = Image.open(BytesIO(response.content)).convert('RGB')
- return image
- except Exception as e:
- logger.error(f"Error downloading image from {image_url}: {str(e)}")
- return None
-
- def extract_dominant_colors(self, image: Image.Image, n_colors: int = 3) -> List[Dict]:
- """Extract dominant colors using K-means clustering."""
- try:
- # Resize for faster processing
- img_small = image.resize((150, 150))
- img_array = np.array(img_small)
- pixels = img_array.reshape(-1, 3)
-
- # K-means clustering
- kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=5)
- kmeans.fit(pixels)
-
- colors = []
- labels_counts = np.bincount(kmeans.labels_)
-
- for i, center in enumerate(kmeans.cluster_centers_):
- rgb = tuple(center.astype(int))
- color_name = self._get_color_name_simple(rgb)
- percentage = float(labels_counts[i] / len(kmeans.labels_) * 100)
-
- colors.append({
- "name": color_name,
- "rgb": rgb,
- "percentage": round(percentage, 2)
- })
-
- # Sort by percentage (most dominant first)
- colors.sort(key=lambda x: x['percentage'], reverse=True)
- return colors
-
- except Exception as e:
- logger.error(f"Error extracting colors: {str(e)}")
- return []
-
- def _get_color_name_simple(self, rgb: Tuple[int, int, int]) -> str:
- """Map RGB values to basic color names."""
- r, g, b = rgb
-
- # Define color ranges with priorities
- colors = {
- 'black': (r < 50 and g < 50 and b < 50),
- 'white': (r > 200 and g > 200 and b > 200),
- 'gray': (abs(r - g) < 30 and abs(g - b) < 30 and abs(r - b) < 30 and 50 <= r <= 200),
- 'red': (r > 150 and g < 100 and b < 100),
- 'green': (g > 150 and r < 100 and b < 100),
- 'blue': (b > 150 and r < 100 and g < 100),
- 'yellow': (r > 200 and g > 200 and b < 100),
- 'orange': (r > 200 and 100 < g < 200 and b < 100),
- 'purple': (r > 100 and b > 100 and g < 100),
- 'pink': (r > 200 and 100 < g < 200 and 100 < b < 200),
- 'brown': (50 < r < 150 and 30 < g < 100 and b < 80),
- 'cyan': (r < 100 and g > 150 and b > 150),
- 'beige': (180 < r < 240 and 160 < g < 220 and 120 < b < 180),
- }
-
- for color_name, condition in colors.items():
- if condition:
- return color_name
-
- # Fallback to dominant channel
- if r > g and r > b:
- return 'red'
- elif g > r and g > b:
- return 'green'
- elif b > r and b > g:
- return 'blue'
- else:
- return 'gray'
-
- # def classify_with_clip(
- # self,
- # image: Image.Image,
- # candidates: List[str],
- # attribute_name: str,
- # confidence_threshold: float = 0.15
- # ) -> Dict:
- # """Use CLIP to classify image against candidate labels."""
- # try:
- # model, processor = self._get_clip_model()
- # device = self._get_device()
-
- # # Prepare inputs
- # inputs = processor(
- # text=candidates,
- # images=image,
- # return_tensors="pt",
- # padding=True
- # )
-
- # # Move to device
- # inputs = {k: v.to(device) for k, v in inputs.items()}
-
- # # Get predictions
- # with torch.no_grad():
- # outputs = model(**inputs)
- # logits_per_image = outputs.logits_per_image
- # probs = logits_per_image.softmax(dim=1).cpu()
-
- # # Get top predictions
- # top_k = min(3, len(candidates))
- # top_probs, top_indices = torch.topk(probs[0], k=top_k)
-
- # results = []
- # for prob, idx in zip(top_probs, top_indices):
- # if prob.item() > confidence_threshold:
- # results.append({
- # "value": candidates[idx.item()],
- # "confidence": round(float(prob.item()), 3)
- # })
-
- # return {
- # "attribute": attribute_name,
- # "predictions": results
- # }
-
- # except Exception as e:
- # logger.error(f"Error in CLIP classification for {attribute_name}: {str(e)}")
- # return {"attribute": attribute_name, "predictions": []}
-
- def classify_with_clip(
- self,
- image: Image.Image,
- candidates: List[str],
- attribute_name: str,
- confidence_threshold: float = 0.15
- ) -> Dict:
- """Use CLIP to classify image against candidate labels."""
- try:
- model, processor = self._get_clip_model()
- device = self._get_device()
-
- # ⚡ OPTIMIZATION: Process in smaller batches to avoid memory issues
- batch_size = 16 # Process 16 candidates at a time
- all_results = []
-
- for i in range(0, len(candidates), batch_size):
- batch_candidates = candidates[i:i + batch_size]
-
- # Prepare inputs WITHOUT progress bars
- inputs = processor(
- text=batch_candidates,
- images=image,
- return_tensors="pt",
- padding=True
- )
-
- # Move to device
- inputs = {k: v.to(device) for k, v in inputs.items()}
-
- # Get predictions
- with torch.no_grad():
- outputs = model(**inputs)
- logits_per_image = outputs.logits_per_image
- probs = logits_per_image.softmax(dim=1).cpu()
-
- # Collect results from this batch
- for j, prob in enumerate(probs[0]):
- if prob.item() > confidence_threshold:
- all_results.append({
- "value": batch_candidates[j],
- "confidence": round(float(prob.item()), 3)
- })
-
- # Sort by confidence and return top 3
- all_results.sort(key=lambda x: x['confidence'], reverse=True)
-
- return {
- "attribute": attribute_name,
- "predictions": all_results[:3]
- }
-
- except Exception as e:
- logger.error(f"Error in CLIP classification for {attribute_name}: {str(e)}")
- return {"attribute": attribute_name, "predictions": []}
- def detect_category_and_subcategory(self, image: Image.Image) -> Tuple[str, str, str, float]:
- """
- Hierarchically detect category, subcategory, and specific product.
- Returns: (category, subcategory, product_type, confidence)
- """
- # Step 1: Detect if it's clothing or something else
- main_categories = list(self.CATEGORY_ATTRIBUTES.keys())
- category_prompts = [f"a photo of {cat}" for cat in main_categories]
-
- result = self.classify_with_clip(image, category_prompts, "main_category", confidence_threshold=0.10)
-
- if not result["predictions"]:
- return "unknown", "unknown", "unknown", 0.0
-
- detected_category = result["predictions"][0]["value"].replace("a photo of ", "")
- category_confidence = result["predictions"][0]["confidence"]
-
- logger.info(f"Step 1 - Main category detected: {detected_category} (confidence: {category_confidence:.3f})")
-
- # Step 2: For clothing, detect subcategory (tops/bottoms/dresses/outerwear)
- if detected_category == "clothing":
- subcategories = self.CATEGORY_ATTRIBUTES["clothing"]["subcategories"]
-
- # Collect all products grouped by subcategory
- all_products = []
- product_to_subcategory = {}
-
- for subcat, subcat_data in subcategories.items():
- for product in subcat_data["products"]:
- prompt = f"a photo of {product}"
- all_products.append(prompt)
- product_to_subcategory[prompt] = subcat
-
- # Step 3: Detect specific product type
- product_result = self.classify_with_clip(
- image,
- all_products,
- "product_type",
- confidence_threshold=0.12
- )
-
- if product_result["predictions"]:
- best_match = product_result["predictions"][0]
- product_prompt = best_match["value"]
- product_type = product_prompt.replace("a photo of ", "")
- subcategory = product_to_subcategory[product_prompt]
- product_confidence = best_match["confidence"]
-
- logger.info(f"Step 2 - Detected: {subcategory} > {product_type} (confidence: {product_confidence:.3f})")
- return detected_category, subcategory, product_type, product_confidence
- else:
- logger.warning("Could not detect specific product type for clothing")
- return detected_category, "unknown", "unknown", category_confidence
-
- # Step 3: For non-clothing categories, just detect product type
- else:
- category_data = self.CATEGORY_ATTRIBUTES[detected_category]
-
- # Check if this category has subcategories or direct products
- if "products" in category_data:
- products = category_data["products"]
- product_prompts = [f"a photo of {p}" for p in products]
-
- product_result = self.classify_with_clip(
- image,
- product_prompts,
- "product_type",
- confidence_threshold=0.12
- )
-
- if product_result["predictions"]:
- best_match = product_result["predictions"][0]
- product_type = best_match["value"].replace("a photo of ", "")
-
- logger.info(f"Step 2 - Detected: {detected_category} > {product_type}")
- return detected_category, "none", product_type, best_match["confidence"]
-
- return detected_category, "unknown", "unknown", category_confidence
-
- def process_image(
- self,
- image_url: str,
- product_type_hint: Optional[str] = None
- ) -> Dict:
- """
- Main method to process image and extract visual attributes.
- Uses hierarchical detection to extract only relevant attributes.
- """
- import time
- start_time = time.time()
-
- try:
- # Download image
- image = self.download_image(image_url)
- if image is None:
- return {
- "visual_attributes": {},
- "error": "Failed to download image"
- }
-
- visual_attributes = {}
- detailed_predictions = {}
-
- # Step 1: Detect category, subcategory, and product type
- category, subcategory, product_type, confidence = self.detect_category_and_subcategory(image)
-
- # Low confidence check
- if confidence < 0.10:
- logger.warning(f"Low confidence in detection ({confidence:.3f}). Returning basic attributes only.")
- colors = self.extract_dominant_colors(image, n_colors=3)
- if colors:
- visual_attributes["primary_color"] = colors[0]["name"]
- visual_attributes["color_palette"] = [c["name"] for c in colors]
-
- return {
- "visual_attributes": visual_attributes,
- "detection_confidence": confidence,
- "warning": "Low confidence detection",
- "processing_time": round(time.time() - start_time, 2)
- }
-
- # Add detected metadata
- visual_attributes["product_type"] = product_type
- visual_attributes["category"] = category
- if subcategory != "none" and subcategory != "unknown":
- visual_attributes["subcategory"] = subcategory
-
- # Step 2: Extract color information (universal)
- colors = self.extract_dominant_colors(image, n_colors=3)
- if colors:
- visual_attributes["primary_color"] = colors[0]["name"]
- visual_attributes["color_palette"] = [c["name"] for c in colors[:3]]
- visual_attributes["color_distribution"] = [
- {"color": c["name"], "percentage": c["percentage"]}
- for c in colors
- ]
-
- # Step 3: Get the right attribute configuration based on subcategory
- attributes_config = None
-
- if category == "clothing":
- if subcategory in self.CATEGORY_ATTRIBUTES["clothing"]["subcategories"]:
- attributes_config = self.CATEGORY_ATTRIBUTES["clothing"]["subcategories"][subcategory]["attributes"]
- logger.info(f"Using attributes for subcategory: {subcategory}")
- else:
- logger.warning(f"Unknown subcategory: {subcategory}. Skipping attribute extraction.")
-
- elif category in self.CATEGORY_ATTRIBUTES:
- if "attributes" in self.CATEGORY_ATTRIBUTES[category]:
- attributes_config = self.CATEGORY_ATTRIBUTES[category]["attributes"]
- logger.info(f"Using attributes for category: {category}")
-
- # Step 4: Extract category-specific attributes
- if attributes_config:
- for attr_name, attr_values in attributes_config.items():
- result = self.classify_with_clip(
- image,
- attr_values,
- attr_name,
- confidence_threshold=0.20
- )
-
- if result["predictions"]:
- best_prediction = result["predictions"][0]
- # Only add attributes with reasonable confidence
- if best_prediction["confidence"] > 0.20:
- visual_attributes[attr_name] = best_prediction["value"]
-
- # Store detailed predictions for debugging
- detailed_predictions[attr_name] = result
-
- processing_time = time.time() - start_time
-
- logger.info(f"✓ Processing complete in {processing_time:.2f}s. Extracted {len(visual_attributes)} attributes.")
-
- return {
- "visual_attributes": visual_attributes,
- "detailed_predictions": detailed_predictions,
- "detection_confidence": confidence,
- "processing_time": round(processing_time, 2)
- }
-
- except Exception as e:
- logger.error(f"Error processing image: {str(e)}")
- return {
- "visual_attributes": {},
- "error": str(e),
- "processing_time": round(time.time() - start_time, 2)
- }
- # # ==================== visual_processing_service_enhanced.py ====================
- # """
- # Enhanced Visual Processing Service combining CLIP's speed with BLIP-2's comprehensive taxonomy.
- # Features:
- # - Fast CLIP-based classification
- # - 70+ product categories across multiple domains
- # - Two-stage classification with validation
- # - Enhanced color normalization
- # - Category-specific attribute detection
- # - Confidence-based fallback mechanisms
- # - Optional center cropping for better focus
- # Usage:
- # service = VisualProcessingService()
- # result = service.process_image("https://example.com/product.jpg")
- # """
- # import torch
- # import cv2
- # import numpy as np
- # import requests
- # from io import BytesIO
- # from PIL import Image
- # from typing import Dict, List, Optional, Tuple
- # import logging
- # from transformers import CLIPProcessor, CLIPModel
- # from sklearn.cluster import KMeans
- # logger = logging.getLogger(__name__)
- # class VisualProcessingService:
- # """Enhanced service for extracting visual attributes from product images using CLIP."""
-
- # # Class-level caching (shared across instances)
- # _clip_model = None
- # _clip_processor = None
- # _device = None
-
- # # ==================== EXPANDED TAXONOMY ====================
-
- # # Base color vocabulary
- # COLORS = ["black", "white", "red", "blue", "green", "yellow", "gray",
- # "brown", "pink", "purple", "orange", "beige", "navy", "teal"]
-
- # # Pattern vocabulary
- # PATTERNS = ["solid", "striped", "checked", "plaid", "floral", "graphic",
- # "polka dot", "camo", "tie-dye", "abstract", "geometric"]
-
- # # Material vocabulary (extended)
- # MATERIALS = ["cotton", "polyester", "denim", "leather", "wool", "canvas",
- # "silicone", "metal", "fabric", "rubber", "plastic", "wood",
- # "glass", "ceramic", "steel", "foam", "aluminum", "carbon fiber"]
-
- # # Style vocabulary
- # STYLES = ["casual", "formal", "sporty", "streetwear", "elegant", "vintage",
- # "modern", "bohemian", "minimalist", "industrial", "rustic", "contemporary"]
-
- # # Fit vocabulary
- # FITS = ["slim fit", "regular fit", "loose fit", "oversized", "tailored",
- # "relaxed", "athletic fit"]
-
- # # Brand vocabulary (common marketplace brands)
- # BRANDS = ["nike", "adidas", "sony", "samsung", "apple", "generic", "lego",
- # "hasbro", "lg", "panasonic", "microsoft"]
-
- # # Age group vocabulary
- # AGE_GROUPS = ["baby", "toddler", "child", "teen", "adult", "all ages"]
-
- # # Comprehensive category-specific attributes
- # CATEGORY_ATTRIBUTES = {
- # # ==================== CLOTHING ====================
- # "clothing": {
- # "products": ["t-shirt", "shirt", "dress", "pants", "jeans", "shorts",
- # "skirt", "jacket", "coat", "sweater", "hoodie", "top",
- # "blouse", "cardigan", "blazer"],
- # "attributes": {
- # "color": COLORS,
- # "pattern": PATTERNS,
- # "material": ["cotton", "polyester", "denim", "leather", "silk",
- # "wool", "linen", "blend", "canvas"],
- # "style": STYLES,
- # "fit": FITS,
- # "neckline": ["crew neck", "v-neck", "round neck", "collar",
- # "scoop neck", "boat neck", "turtleneck"],
- # "sleeve_type": ["short sleeve", "long sleeve", "sleeveless",
- # "3/4 sleeve", "cap sleeve"],
- # "closure_type": ["button", "zipper", "pull-on", "snap", "tie", "buckle"]
- # }
- # },
-
- # # ==================== FOOTWEAR ====================
- # "footwear": {
- # "products": ["shoes", "sneakers", "sandals", "boots", "slippers",
- # "heels", "loafers"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["leather", "synthetic", "canvas", "rubber", "suede", "fabric"],
- # "type": ["sneakers", "sandals", "formal", "boots", "sports", "casual"],
- # "style": STYLES,
- # "closure_type": ["lace-up", "slip-on", "velcro", "zipper", "buckle"]
- # }
- # },
-
- # # ==================== ACCESSORIES ====================
- # "accessories": {
- # "products": ["watch", "bag", "backpack", "handbag", "wallet", "belt",
- # "sunglasses", "hat", "scarf"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["leather", "fabric", "metal", "plastic", "canvas", "synthetic"],
- # "style": STYLES,
- # "type": ["backpack", "tote", "crossbody", "messenger", "duffel"]
- # }
- # },
-
- # # ==================== JEWELRY ====================
- # "jewelry": {
- # "products": ["necklace", "ring", "bracelet", "earrings", "pendant", "chain"],
- # "attributes": {
- # "material": ["gold", "silver", "platinum", "stainless steel",
- # "plastic", "beads", "leather"],
- # "style": ["modern", "vintage", "minimalist", "statement", "elegant"],
- # "type": ["chain", "band", "solitaire", "hoop", "stud"]
- # }
- # },
-
- # # ==================== ELECTRONICS ====================
- # "electronics": {
- # "products": ["phone", "smartphone", "tablet", "laptop", "headphones",
- # "camera", "tv", "monitor", "keyboard", "mouse", "speaker",
- # "smartwatch", "charger"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["plastic", "metal", "glass", "aluminum", "rubber"],
- # "style": ["modern", "minimalist", "sleek", "industrial"],
- # "finish": ["matte", "glossy", "metallic", "textured"],
- # "type": ["over-ear", "in-ear", "on-ear", "wireless", "wired"],
- # "brand": BRANDS
- # }
- # },
-
- # # ==================== FURNITURE ====================
- # "furniture": {
- # "products": ["chair", "table", "sofa", "bed", "desk", "shelf",
- # "cabinet", "dresser", "bench", "stool", "bookshelf"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["wood", "metal", "glass", "plastic", "fabric", "leather"],
- # "style": ["modern", "traditional", "industrial", "rustic",
- # "contemporary", "vintage", "minimalist"],
- # "finish": ["natural wood", "painted", "stained", "laminated", "upholstered"]
- # }
- # },
-
- # # ==================== HOME DECOR ====================
- # "home_decor": {
- # "products": ["painting", "canvas", "wall art", "frame", "vase", "lamp",
- # "mirror", "clock", "sculpture", "poster", "cushion", "rug"],
- # "attributes": {
- # "color": COLORS,
- # "style": ["modern", "abstract", "traditional", "contemporary",
- # "vintage", "minimalist", "bohemian"],
- # "material": ["canvas", "wood", "metal", "glass", "ceramic", "paper", "fabric"],
- # "finish": ["glossy", "matte", "textured", "framed"],
- # "theme": ["nature", "geometric", "floral", "landscape", "abstract"]
- # }
- # },
-
- # # ==================== KITCHEN ====================
- # "kitchen": {
- # "products": ["pot", "pan", "knife", "utensil", "plate", "bowl", "cup",
- # "mug", "bottle", "container", "cutting board"],
- # "attributes": {
- # "material": ["stainless steel", "aluminum", "ceramic", "glass",
- # "plastic", "wood", "silicone"],
- # "finish": ["non-stick", "stainless", "enameled", "anodized"],
- # "type": ["frypan", "saucepan", "chef knife", "utility", "mixing"]
- # }
- # },
-
- # # ==================== APPLIANCES ====================
- # "appliances": {
- # "products": ["microwave", "blender", "vacuum", "fan", "toaster",
- # "coffee maker", "iron", "hair dryer"],
- # "attributes": {
- # "color": COLORS,
- # "type": ["upright", "robot", "handheld", "ceiling", "table", "tower"],
- # "power": ["low", "medium", "high", "variable"],
- # "brand": BRANDS
- # }
- # },
-
- # # ==================== BEAUTY & PERSONAL CARE ====================
- # "beauty": {
- # "products": ["lipstick", "perfume", "lotion", "hair dryer", "makeup",
- # "skincare", "nail polish", "shampoo"],
- # "attributes": {
- # "color": COLORS,
- # "type": ["eau de parfum", "eau de toilette", "body spray",
- # "body lotion", "face cream"],
- # "finish": ["matte", "glossy", "satin", "shimmer"]
- # }
- # },
-
- # # ==================== TOYS ====================
- # "toys": {
- # "products": ["doll", "puzzle", "board game", "action figure", "plush toy",
- # "toy car", "lego", "building blocks"],
- # "attributes": {
- # "color": COLORS,
- # "age_group": AGE_GROUPS,
- # "material": ["plastic", "wood", "fabric", "metal", "foam"],
- # "type": ["educational", "plush", "action", "vehicle", "puzzle", "board game"],
- # "brand": BRANDS
- # }
- # },
-
- # # ==================== SPORTS & OUTDOOR ====================
- # "sports": {
- # "products": ["bicycle", "football", "basketball", "tennis racket",
- # "yoga mat", "helmet", "skateboard", "dumbbells", "ball"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["steel", "aluminum", "carbon fiber", "rubber",
- # "leather", "synthetic", "foam", "composite"],
- # "sport_type": ["football", "basketball", "tennis", "cycling",
- # "yoga", "gym", "outdoor", "fitness"],
- # "type": ["mountain", "road", "hybrid", "bmx", "indoor", "outdoor"],
- # "brand": BRANDS
- # }
- # },
-
- # # ==================== PET SUPPLIES ====================
- # "pet_supplies": {
- # "products": ["pet bed", "pet toy", "leash", "pet bowl", "collar",
- # "pet carrier"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["fabric", "plastic", "metal", "nylon", "leather"],
- # "size": ["small", "medium", "large", "extra large"]
- # }
- # },
-
- # # ==================== BABY PRODUCTS ====================
- # "baby": {
- # "products": ["stroller", "baby bottle", "diaper", "crib", "high chair",
- # "baby carrier"],
- # "attributes": {
- # "color": COLORS,
- # "material": MATERIALS,
- # "type": ["full-size", "umbrella", "jogging", "disposable", "cloth"],
- # "age_group": ["newborn", "baby", "toddler"]
- # }
- # },
-
- # # ==================== TOOLS & HARDWARE ====================
- # "tools": {
- # "products": ["hammer", "drill", "screwdriver", "wrench", "saw",
- # "pliers", "measuring tape", "level"],
- # "attributes": {
- # "material": ["steel", "aluminum", "plastic", "wood", "rubber",
- # "chrome", "fiberglass"],
- # "type": ["manual", "electric", "cordless", "corded", "pneumatic"],
- # "finish": ["chrome plated", "powder coated", "stainless steel"],
- # "brand": BRANDS
- # }
- # },
-
- # # ==================== BOOKS & MEDIA ====================
- # "books_media": {
- # "products": ["book", "magazine", "dvd", "video game", "cd", "vinyl"],
- # "attributes": {
- # "type": ["paperback", "hardcover", "ebook", "audiobook"],
- # "genre": ["fiction", "non-fiction", "educational", "kids",
- # "action", "adventure", "sports", "rpg"]
- # }
- # },
-
- # # ==================== AUTOMOTIVE ====================
- # "automotive": {
- # "products": ["car accessory", "tire", "car seat", "steering wheel cover",
- # "floor mat"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["rubber", "plastic", "fabric", "leather", "vinyl"],
- # "type": ["universal", "custom fit"]
- # }
- # },
-
- # # ==================== OFFICE SUPPLIES ====================
- # "office": {
- # "products": ["pen", "notebook", "folder", "desk organizer", "stapler",
- # "calculator", "paper"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["paper", "plastic", "metal", "cardboard"],
- # "type": ["ruled", "blank", "grid", "dot grid"]
- # }
- # },
-
- # # ==================== GARDEN & OUTDOOR ====================
- # "garden": {
- # "products": ["plant pot", "garden tool", "watering can", "planter",
- # "garden hose", "lawn mower"],
- # "attributes": {
- # "color": COLORS,
- # "material": ["ceramic", "plastic", "metal", "terracotta", "wood"],
- # "type": ["indoor", "outdoor", "hanging", "standing"]
- # }
- # }
- # }
-
- # # Attribute-specific confidence thresholds
- # CONFIDENCE_THRESHOLDS = {
- # "color": 0.20,
- # "pattern": 0.25,
- # "material": 0.30,
- # "style": 0.20,
- # "fit": 0.25,
- # "brand": 0.40,
- # "type": 0.22,
- # "finish": 0.28,
- # "neckline": 0.23,
- # "sleeve_type": 0.23
- # }
-
- # def __init__(self):
- # pass
-
- # @classmethod
- # def _get_device(cls):
- # """Get optimal device."""
- # if cls._device is None:
- # cls._device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- # logger.info(f"Visual Processing using device: {cls._device}")
- # return cls._device
-
- # @classmethod
- # def _get_clip_model(cls):
- # """Lazy load CLIP model with class-level caching."""
- # if cls._clip_model is None:
- # logger.info("Loading CLIP model (this may take a few minutes on first use)...")
- # cls._clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
- # cls._clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
-
- # device = cls._get_device()
- # cls._clip_model.to(device)
- # cls._clip_model.eval()
-
- # logger.info("✓ CLIP model loaded successfully")
- # return cls._clip_model, cls._clip_processor
-
- # def center_crop(self, image: Image.Image, rel_crop: float = 0.7) -> Image.Image:
- # """
- # Center-crop to focus on the product area if there is too much background.
-
- # Args:
- # image: PIL Image
- # rel_crop: Relative crop size (0.7 = 70% of min dimension)
- # """
- # w, h = image.size
- # side = int(min(w, h) * rel_crop)
- # left = (w - side) // 2
- # top = (h - side) // 2
- # return image.crop((left, top, left + side, top + side))
-
- # def download_image(self, image_url: str, apply_crop: bool = False,
- # max_size: Tuple[int, int] = (1024, 1024)) -> Optional[Image.Image]:
- # """
- # Download image from URL with optional preprocessing.
-
- # Args:
- # image_url: URL of the image
- # apply_crop: Whether to apply center crop
- # max_size: Maximum dimensions for resizing
- # """
- # try:
- # response = requests.get(image_url, timeout=10)
- # response.raise_for_status()
- # image = Image.open(BytesIO(response.content)).convert('RGB')
-
- # # Resize if too large
- # if image.size[0] > max_size[0] or image.size[1] > max_size[1]:
- # image.thumbnail(max_size, Image.Resampling.LANCZOS)
-
- # # Optional center crop
- # if apply_crop:
- # image = self.center_crop(image, rel_crop=0.7)
-
- # return image
- # except Exception as e:
- # logger.error(f"Error downloading image from {image_url}: {str(e)}")
- # return None
-
- # def normalize_color(self, word: str) -> str:
- # """
- # Enhanced color normalization with aliases and modifiers.
-
- # Args:
- # word: Color word to normalize
- # """
- # w = word.lower().strip()
-
- # # Remove light/dark modifiers
- # w = w.replace("light ", "").replace("dark ", "")
- # w = w.replace("bright ", "").replace("pale ", "")
-
- # # Alias mapping
- # aliases = {
- # "grey": "gray",
- # "navy": "blue",
- # "navy blue": "blue",
- # "maroon": "red",
- # "crimson": "red",
- # "scarlet": "red",
- # "teal": "green",
- # "turquoise": "blue",
- # "cyan": "blue",
- # "indigo": "blue",
- # "violet": "purple",
- # "lavender": "purple",
- # "magenta": "pink",
- # "off white": "white",
- # "off-white": "white",
- # "cream": "beige",
- # "ivory": "white",
- # "khaki": "beige",
- # "tan": "brown",
- # "bronze": "brown",
- # "gold": "yellow",
- # "silver": "gray",
- # "charcoal": "gray"
- # }
-
- # normalized = aliases.get(w, w)
-
- # # Validate against canonical colors
- # if normalized not in [c.lower() for c in self.COLORS]:
- # # Try first word if it's a compound
- # first_word = normalized.split()[0] if ' ' in normalized else normalized
- # if first_word in [c.lower() for c in self.COLORS]:
- # return first_word
-
- # return normalized
-
- # def extract_dominant_colors(self, image: Image.Image, n_colors: int = 3) -> List[Dict]:
- # """Extract dominant colors using K-means clustering."""
- # try:
- # # Resize for faster processing
- # img_small = image.resize((150, 150))
- # img_array = np.array(img_small)
- # pixels = img_array.reshape(-1, 3)
-
- # # Sample if too many pixels
- # if len(pixels) > 10000:
- # indices = np.random.choice(len(pixels), 10000, replace=False)
- # pixels = pixels[indices]
-
- # # K-means clustering
- # kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=5, max_iter=100)
- # kmeans.fit(pixels)
-
- # colors = []
- # labels_counts = np.bincount(kmeans.labels_)
-
- # for i, center in enumerate(kmeans.cluster_centers_):
- # rgb = tuple(center.astype(int))
- # color_name = self._get_color_name_simple(rgb)
- # percentage = float(labels_counts[i] / len(kmeans.labels_) * 100)
-
- # colors.append({
- # "name": color_name,
- # "rgb": rgb,
- # "percentage": percentage
- # })
-
- # colors.sort(key=lambda x: x['percentage'], reverse=True)
- # return colors
-
- # except Exception as e:
- # logger.error(f"Error extracting colors: {str(e)}")
- # return []
-
- # def _get_color_name_simple(self, rgb: Tuple[int, int, int]) -> str:
- # """Simple RGB to color name mapping."""
- # r, g, b = rgb
-
- # # Define color ranges
- # colors = {
- # 'black': (r < 50 and g < 50 and b < 50),
- # 'white': (r > 200 and g > 200 and b > 200),
- # 'gray': (abs(r - g) < 30 and abs(g - b) < 30 and abs(r - b) < 30 and 50 <= r <= 200),
- # 'red': (r > 150 and g < 100 and b < 100),
- # 'green': (g > 150 and r < 100 and b < 100),
- # 'blue': (b > 150 and r < 100 and g < 100),
- # 'yellow': (r > 200 and g > 200 and b < 100),
- # 'orange': (r > 200 and 100 < g < 200 and b < 100),
- # 'purple': (r > 100 and b > 100 and g < 100),
- # 'pink': (r > 200 and 100 < g < 200 and 100 < b < 200),
- # 'brown': (50 < r < 150 and 30 < g < 100 and b < 80),
- # 'beige': (150 < r < 220 and 140 < g < 200 and 100 < b < 180),
- # }
-
- # for color_name, condition in colors.items():
- # if condition:
- # return color_name
-
- # # Fallback based on dominant channel
- # if r > g and r > b:
- # return 'red'
- # elif g > r and g > b:
- # return 'green'
- # elif b > r and b > g:
- # return 'blue'
- # else:
- # return 'gray'
-
- # def classify_with_clip(
- # self,
- # image: Image.Image,
- # candidates: List[str],
- # attribute_name: str,
- # confidence_threshold: Optional[float] = None
- # ) -> Dict:
- # """
- # Use CLIP to classify image against candidate labels.
-
- # Args:
- # image: PIL Image
- # candidates: List of text labels to classify against
- # attribute_name: Name of the attribute being classified
- # confidence_threshold: Override default threshold
- # """
- # try:
- # model, processor = self._get_clip_model()
- # device = self._get_device()
-
- # # Use attribute-specific threshold if not provided
- # if confidence_threshold is None:
- # confidence_threshold = self.CONFIDENCE_THRESHOLDS.get(attribute_name, 0.20)
-
- # # Prepare inputs
- # inputs = processor(
- # text=candidates,
- # images=image,
- # return_tensors="pt",
- # padding=True
- # )
-
- # # Move to device
- # inputs = {k: v.to(device) for k, v in inputs.items()}
-
- # # Get predictions
- # with torch.no_grad():
- # outputs = model(**inputs)
- # logits_per_image = outputs.logits_per_image
- # probs = logits_per_image.softmax(dim=1).cpu()
-
- # # Get top predictions
- # top_k = min(3, len(candidates))
- # top_probs, top_indices = torch.topk(probs[0], k=top_k)
-
- # results = []
- # for prob, idx in zip(top_probs, top_indices):
- # if prob.item() > confidence_threshold:
- # value = candidates[idx.item()]
- # # Apply color normalization if color attribute
- # if attribute_name == "color":
- # value = self.normalize_color(value)
- # results.append({
- # "value": value,
- # "confidence": float(prob.item())
- # })
-
- # return {
- # "attribute": attribute_name,
- # "predictions": results
- # }
-
- # except Exception as e:
- # logger.error(f"Error in CLIP classification for {attribute_name}: {str(e)}")
- # return {"attribute": attribute_name, "predictions": []}
-
- # def detect_category_hierarchical(self, image: Image.Image) -> Tuple[str, str, float]:
- # """
- # Two-stage hierarchical product detection:
- # 1. Detect broad category
- # 2. Detect specific product within that category
-
- # Returns:
- # (category, product_type, confidence)
- # """
- # # Stage 1: Detect broad category
- # category_names = list(self.CATEGORY_ATTRIBUTES.keys())
- # category_labels = [f"a photo of {cat.replace('_', ' ')}" for cat in category_names]
-
- # category_result = self.classify_with_clip(
- # image, category_labels, "category_detection", confidence_threshold=0.15
- # )
-
- # if not category_result["predictions"]:
- # return "unknown", "unknown", 0.0
-
- # # Extract category
- # best_category_match = category_result["predictions"][0]
- # detected_category = category_names[category_labels.index(best_category_match["value"])]
- # category_confidence = best_category_match["confidence"]
-
- # # Stage 2: Detect specific product within category
- # products_in_category = self.CATEGORY_ATTRIBUTES[detected_category]["products"]
- # product_labels = [f"a photo of a {p}" for p in products_in_category]
-
- # product_result = self.classify_with_clip(
- # image, product_labels, "product_detection", confidence_threshold=0.15
- # )
-
- # if product_result["predictions"]:
- # best_product = product_result["predictions"][0]
- # product_type = products_in_category[product_labels.index(best_product["value"])]
- # product_confidence = best_product["confidence"]
-
- # # Combined confidence (geometric mean for balance)
- # combined_confidence = (category_confidence * product_confidence) ** 0.5
-
- # logger.info(f"Detected: {detected_category} → {product_type} (confidence: {combined_confidence:.3f})")
- # return detected_category, product_type, combined_confidence
-
- # return detected_category, "unknown", category_confidence * 0.5
-
- # def detect_category_flat(self, image: Image.Image) -> Tuple[str, str, float]:
- # """
- # Single-stage flat product detection across all categories.
- # Faster but potentially less accurate.
-
- # Returns:
- # (category, product_type, confidence)
- # """
- # # Collect all products with their categories
- # all_products = []
- # product_to_category = {}
-
- # for category, data in self.CATEGORY_ATTRIBUTES.items():
- # for product in data["products"]:
- # label = f"a photo of a {product}"
- # all_products.append(label)
- # product_to_category[label] = category
-
- # # Classify
- # result = self.classify_with_clip(
- # image, all_products, "product_detection", confidence_threshold=0.15
- # )
-
- # if result["predictions"]:
- # best_match = result["predictions"][0]
- # product_label = best_match["value"]
- # category = product_to_category[product_label]
- # product_type = product_label.replace("a photo of a ", "")
- # confidence = best_match["confidence"]
-
- # logger.info(f"Detected: {category} → {product_type} (confidence: {confidence:.3f})")
- # return category, product_type, confidence
-
- # return "unknown", "unknown", 0.0
-
- # def process_image(
- # self,
- # image_url: str,
- # product_type_hint: Optional[str] = None,
- # apply_crop: bool = False,
- # detection_mode: str = "hierarchical"
- # ) -> Dict:
- # """
- # Main method to process image and extract visual attributes.
-
- # Args:
- # image_url: URL of the product image
- # product_type_hint: Optional hint about product type
- # apply_crop: Whether to apply center crop for better focus
- # detection_mode: "hierarchical" (slower, more accurate) or "flat" (faster)
- # """
- # import time
- # start_time = time.time()
-
- # try:
- # # Download image
- # image = self.download_image(image_url, apply_crop=apply_crop)
- # if image is None:
- # return {
- # "visual_attributes": {},
- # "error": "Failed to download image"
- # }
-
- # visual_attributes = {}
- # detailed_predictions = {}
-
- # # Step 1: Detect product category and type
- # if detection_mode == "hierarchical":
- # detected_category, detected_product_type, category_confidence = \
- # self.detect_category_hierarchical(image)
- # else:
- # detected_category, detected_product_type, category_confidence = \
- # self.detect_category_flat(image)
-
- # # If confidence is too low, return minimal info
- # if category_confidence < 0.12:
- # logger.warning(f"Low confidence ({category_confidence:.3f}). Returning basic attributes only.")
- # colors = self.extract_dominant_colors(image, n_colors=3)
- # if colors:
- # visual_attributes["primary_color"] = colors[0]["name"]
- # visual_attributes["color_palette"] = [c["name"] for c in colors]
-
- # return {
- # "visual_attributes": visual_attributes,
- # "category_confidence": category_confidence,
- # "processing_time": round(time.time() - start_time, 2),
- # "warning": "Low confidence detection"
- # }
-
- # # Add detected information
- # visual_attributes["product_type"] = detected_product_type
- # visual_attributes["category"] = detected_category
- # visual_attributes["detection_confidence"] = round(category_confidence, 3)
-
- # # Step 2: Extract universal color attribute
- # colors = self.extract_dominant_colors(image, n_colors=3)
- # if colors:
- # visual_attributes["primary_color"] = colors[0]["name"]
- # visual_attributes["color_palette"] = [c["name"] for c in colors]
- # visual_attributes["color_distribution"] = [
- # {"name": c["name"], "percentage": round(c["percentage"], 1)}
- # for c in colors
- # ]
-
- # # Step 3: Extract category-specific attributes
- # if detected_category in self.CATEGORY_ATTRIBUTES:
- # category_config = self.CATEGORY_ATTRIBUTES[detected_category]
-
- # for attr_name, attr_values in category_config["attributes"].items():
- # # Skip color since we already extracted it
- # if attr_name == "color":
- # continue
-
- # # Get attribute-specific threshold
- # threshold = self.CONFIDENCE_THRESHOLDS.get(attr_name, 0.20)
-
- # # Classify
- # result = self.classify_with_clip(
- # image, attr_values, attr_name, confidence_threshold=threshold
- # )
-
- # detailed_predictions[attr_name] = result
-
- # # Only add if confidence is reasonable
- # if result["predictions"]:
- # best_prediction = result["predictions"][0]
- # if best_prediction["confidence"] > threshold:
- # visual_attributes[attr_name] = best_prediction["value"]
-
- # processing_time = time.time() - start_time
-
- # return {
- # "visual_attributes": visual_attributes,
- # "detailed_predictions": detailed_predictions,
- # "detection_confidence": round(category_confidence, 3),
- # "processing_time": round(processing_time, 2),
- # "metadata": {
- # "detection_mode": detection_mode,
- # "crop_applied": apply_crop,
- # "image_size": image.size
- # }
- # }
-
- # except Exception as e:
- # logger.error(f"Error processing image: {str(e)}")
- # import traceback
- # traceback.print_exc()
- # return {
- # "visual_attributes": {},
- # "error": str(e),
- # "processing_time": round(time.time() - start_time, 2)
- # }
-
- # def batch_process_images(
- # self,
- # image_urls: List[str],
- # detection_mode: str = "flat"
- # ) -> List[Dict]:
- # """
- # Process multiple images in batch.
-
- # Args:
- # image_urls: List of image URLs
- # detection_mode: Detection mode to use
- # """
- # results = []
- # for i, url in enumerate(image_urls):
- # logger.info(f"Processing image {i+1}/{len(image_urls)}: {url}")
- # result = self.process_image(url, detection_mode=detection_mode)
- # results.append(result)
- # return results
-
- # @classmethod
- # def cleanup_models(cls):
- # """Free up memory by unloading models."""
- # if cls._clip_model is not None:
- # del cls._clip_model
- # del cls._clip_processor
- # cls._clip_model = None
- # cls._clip_processor = None
-
- # if torch.cuda.is_available():
- # torch.cuda.empty_cache()
-
- # logger.info("Models unloaded and memory freed")
-
- # def get_supported_categories(self) -> List[str]:
- # """Get list of all supported product categories."""
- # return list(self.CATEGORY_ATTRIBUTES.keys())
-
- # def get_category_products(self, category: str) -> List[str]:
- # """Get list of products in a specific category."""
- # return self.CATEGORY_ATTRIBUTES.get(category, {}).get("products", [])
-
- # def get_category_attributes(self, category: str) -> Dict[str, List[str]]:
- # """Get attribute schema for a specific category."""
- # return self.CATEGORY_ATTRIBUTES.get(category, {}).get("attributes", {})
-
- # def get_statistics(self) -> Dict:
- # """Get statistics about the taxonomy."""
- # total_products = sum(
- # len(data["products"])
- # for data in self.CATEGORY_ATTRIBUTES.values()
- # )
- # total_attributes = sum(
- # len(data["attributes"])
- # for data in self.CATEGORY_ATTRIBUTES.values()
- # )
-
- # return {
- # "total_categories": len(self.CATEGORY_ATTRIBUTES),
- # "total_products": total_products,
- # "total_unique_attributes": len(set(
- # attr
- # for data in self.CATEGORY_ATTRIBUTES.values()
- # for attr in data["attributes"].keys()
- # )),
- # "categories": list(self.CATEGORY_ATTRIBUTES.keys())
- # }
- # # ==================== USAGE EXAMPLES ====================
- # def example_basic_usage():
- # """Basic usage example."""
- # print("=== Basic Usage Example ===\n")
-
- # # Initialize service
- # service = VisualProcessingService()
-
- # # Process single image (hierarchical mode - more accurate)
- # result = service.process_image(
- # "https://example.com/product.jpg",
- # detection_mode="hierarchical"
- # )
-
- # print("Product Type:", result["visual_attributes"].get("product_type"))
- # print("Category:", result["visual_attributes"].get("category"))
- # print("Primary Color:", result["visual_attributes"].get("primary_color"))
- # print("Detection Confidence:", result.get("detection_confidence"))
- # print("Processing Time:", result["processing_time"], "seconds")
- # print("\nAll Attributes:")
- # for key, value in result["visual_attributes"].items():
- # print(f" {key}: {value}")
- # def example_fast_mode():
- # """Fast processing mode example."""
- # print("\n=== Fast Mode Example ===\n")
-
- # service = VisualProcessingService()
-
- # # Fast mode (flat detection)
- # result = service.process_image(
- # "https://example.com/product.jpg",
- # detection_mode="flat" # Faster, single-stage detection
- # )
-
- # print("Processing Time:", result["processing_time"], "seconds")
- # print("Detected:", result["visual_attributes"])
- # def example_with_cropping():
- # """Example with center cropping for busy backgrounds."""
- # print("\n=== With Center Cropping ===\n")
-
- # service = VisualProcessingService()
-
- # # Apply center crop to focus on product
- # result = service.process_image(
- # "https://example.com/product-with-background.jpg",
- # apply_crop=True, # Enable center cropping
- # detection_mode="hierarchical"
- # )
-
- # print("Crop Applied:", result["metadata"]["crop_applied"])
- # print("Detected:", result["visual_attributes"])
- # def example_batch_processing():
- # """Batch processing example."""
- # print("\n=== Batch Processing ===\n")
-
- # service = VisualProcessingService()
-
- # image_urls = [
- # "https://example.com/product1.jpg",
- # "https://example.com/product2.jpg",
- # "https://example.com/product3.jpg"
- # ]
-
- # results = service.batch_process_images(image_urls, detection_mode="flat")
-
- # for i, result in enumerate(results):
- # print(f"\nProduct {i+1}:")
- # print(f" Type: {result['visual_attributes'].get('product_type')}")
- # print(f" Category: {result['visual_attributes'].get('category')}")
- # print(f" Time: {result['processing_time']}s")
- # def example_category_info():
- # """Get information about supported categories."""
- # print("\n=== Category Information ===\n")
-
- # service = VisualProcessingService()
-
- # # Get statistics
- # stats = service.get_statistics()
- # print("Statistics:")
- # print(f" Total Categories: {stats['total_categories']}")
- # print(f" Total Products: {stats['total_products']}")
- # print(f" Unique Attributes: {stats['total_unique_attributes']}")
-
- # # Get all categories
- # categories = service.get_supported_categories()
- # print(f"\nSupported Categories ({len(categories)}):")
- # for cat in categories:
- # products = service.get_category_products(cat)
- # print(f" {cat}: {len(products)} products")
-
- # # Get attributes for a specific category
- # print("\nClothing Category Attributes:")
- # clothing_attrs = service.get_category_attributes("clothing")
- # for attr, values in clothing_attrs.items():
- # print(f" {attr}: {len(values)} options")
- # def example_detailed_predictions():
- # """Example showing detailed predictions with confidence scores."""
- # print("\n=== Detailed Predictions ===\n")
-
- # service = VisualProcessingService()
-
- # result = service.process_image(
- # "https://example.com/product.jpg",
- # detection_mode="hierarchical"
- # )
-
- # print("Visual Attributes (Best Predictions):")
- # for key, value in result["visual_attributes"].items():
- # print(f" {key}: {value}")
-
- # print("\nDetailed Predictions (Top 3 for each attribute):")
- # for attr_name, predictions in result.get("detailed_predictions", {}).items():
- # print(f"\n {attr_name}:")
- # for pred in predictions.get("predictions", []):
- # print(f" - {pred['value']}: {pred['confidence']:.3f}")
- # def example_color_distribution():
- # """Example showing color palette extraction."""
- # print("\n=== Color Distribution ===\n")
-
- # service = VisualProcessingService()
-
- # result = service.process_image("https://example.com/product.jpg")
-
- # print("Primary Color:", result["visual_attributes"].get("primary_color"))
- # print("\nColor Palette:")
- # for color in result["visual_attributes"].get("color_palette", []):
- # print(f" - {color}")
-
- # print("\nColor Distribution:")
- # for color_info in result["visual_attributes"].get("color_distribution", []):
- # print(f" {color_info['name']}: {color_info['percentage']}%")
- # def example_error_handling():
- # """Example showing error handling."""
- # print("\n=== Error Handling ===\n")
-
- # service = VisualProcessingService()
-
- # # Invalid URL
- # result = service.process_image("https://invalid-url.com/nonexistent.jpg")
-
- # if "error" in result:
- # print("Error occurred:", result["error"])
- # else:
- # print("Processing successful")
-
- # # Low confidence warning
- # result = service.process_image("https://example.com/ambiguous-product.jpg")
-
- # if "warning" in result:
- # print("Warning:", result["warning"])
- # print("Confidence:", result.get("category_confidence"))
- # def example_cleanup():
- # """Example showing model cleanup."""
- # print("\n=== Model Cleanup ===\n")
-
- # service = VisualProcessingService()
-
- # # Process some images
- # result = service.process_image("https://example.com/product.jpg")
- # print("Processed successfully")
-
- # # Clean up models when done (frees memory)
- # VisualProcessingService.cleanup_models()
- # print("Models cleaned up and memory freed")
- # # ==================== PRODUCTION USAGE ====================
- # def production_example():
- # """
- # Production-ready example with proper error handling and logging.
- # """
- # import logging
-
- # # Setup logging
- # logging.basicConfig(
- # level=logging.INFO,
- # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
- # )
-
- # service = VisualProcessingService()
-
- # def process_product_image(image_url: str, product_id: str) -> Dict:
- # """
- # Process a product image with full error handling.
- # """
- # try:
- # # Process with hierarchical mode for best accuracy
- # result = service.process_image(
- # image_url,
- # detection_mode="hierarchical",
- # apply_crop=False # Set True if images have busy backgrounds
- # )
-
- # # Check for errors
- # if "error" in result:
- # logger.error(f"Failed to process {product_id}: {result['error']}")
- # return {
- # "product_id": product_id,
- # "status": "error",
- # "error": result["error"]
- # }
-
- # # Check confidence
- # confidence = result.get("detection_confidence", 0)
- # if confidence < 0.15:
- # logger.warning(f"Low confidence for {product_id}: {confidence}")
- # return {
- # "product_id": product_id,
- # "status": "low_confidence",
- # "confidence": confidence,
- # "partial_attributes": result["visual_attributes"]
- # }
-
- # # Success
- # return {
- # "product_id": product_id,
- # "status": "success",
- # "attributes": result["visual_attributes"],
- # "confidence": confidence,
- # "processing_time": result["processing_time"]
- # }
-
- # except Exception as e:
- # logger.exception(f"Unexpected error processing {product_id}")
- # return {
- # "product_id": product_id,
- # "status": "exception",
- # "error": str(e)
- # }
-
- # # Process products
- # products = [
- # {"id": "PROD001", "image_url": "https://example.com/tshirt.jpg"},
- # {"id": "PROD002", "image_url": "https://example.com/laptop.jpg"},
- # {"id": "PROD003", "image_url": "https://example.com/chair.jpg"}
- # ]
-
- # results = []
- # for product in products:
- # result = process_product_image(product["image_url"], product["id"])
- # results.append(result)
-
- # # Print summary
- # if result["status"] == "success":
- # attrs = result["attributes"]
- # print(f"\n✓ {product['id']} ({result['processing_time']}s):")
- # print(f" Type: {attrs.get('product_type')}")
- # print(f" Category: {attrs.get('category')}")
- # print(f" Color: {attrs.get('primary_color')}")
- # else:
- # print(f"\n✗ {product['id']}: {result['status']}")
-
- # return results
- # # ==================== MAIN ====================
- # if __name__ == "__main__":
- # # Run examples
- # print("Enhanced Visual Processing Service")
- # print("=" * 60)
-
- # # Show statistics
- # service = VisualProcessingService()
- # stats = service.get_statistics()
- # print(f"\nTaxonomy Coverage:")
- # print(f" Categories: {stats['total_categories']}")
- # print(f" Products: {stats['total_products']}")
- # print(f" Attributes: {stats['total_unique_attributes']}")
-
- # print("\n" + "=" * 60)
- # print("Run individual examples by calling the example functions:")
- # print(" - example_basic_usage()")
- # print(" - example_fast_mode()")
- # print(" - example_with_cropping()")
- # print(" - example_batch_processing()")
- # print(" - example_category_info()")
- # print(" - example_detailed_predictions()")
- # print(" - example_color_distribution()")
- # print(" - production_example()")
- # print("=" * 60)
|