Spaces:
Sleeping
Sleeping
| import torch | |
| import torch.nn as nn | |
| from torchvision import transforms, models | |
| import open_clip | |
| from PIL import Image, ImageFilter | |
| import numpy as np | |
| import os | |
| # --- 1. SETUP & DEVICE --- | |
| DEVICE = torch.device("mps") if torch.backends.mps.is_available() else torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Using device: {DEVICE}") | |
| # --- 2. LOAD MODELS --- | |
| # A. Load openclip (ViT-L-14) | |
| print("Loading openclip...") | |
| openclip_model, _, openclip_preprocess = open_clip.create_model_and_transforms( | |
| 'ViT-L-14', pretrained='datacomp_xl_s13b_b90k' | |
| ) | |
| openclip_model.to(DEVICE) | |
| # Define your openclip Forensic Head Architecture (matches your training) | |
| class openclipHead(nn.Module): | |
| def __init__(self, input_dim): | |
| super().__init__() | |
| self.net = nn.Sequential( | |
| nn.Linear(input_dim, 512), | |
| nn.ReLU(), | |
| nn.Dropout(0.3), | |
| nn.Linear(512, 1) | |
| ) | |
| def forward(self, x): return self.net(x) | |
| # Load openclip Weights | |
| openclip_head = openclipHead(input_dim=768).to(DEVICE) | |
| openclip_head.load_state_dict(torch.load('models/openclip_forensic_head.pth', map_location=DEVICE)) | |
| openclip_head.eval() | |
| # B. Load ConvNeXt-Base | |
| print("Loading ConvNeXt...") | |
| cn_backbone = models.convnext_base(weights=None) # Architecture only | |
| cn_backbone.to(DEVICE) | |
| cn_backbone.eval() | |
| class ConvNextHead(nn.Module): | |
| def __init__(self, input_dim): | |
| super().__init__() | |
| self.net = nn.Sequential( | |
| nn.Linear(input_dim, 512), | |
| nn.ReLU(), | |
| nn.Dropout(0.3), | |
| nn.Linear(512, 1) | |
| ) | |
| def forward(self, x): return self.net(x) | |
| cn_head = ConvNextHead(input_dim=1024).to(DEVICE) | |
| cn_head.load_state_dict(torch.load('models/convnext_forensic_head.pth', map_location=DEVICE)) | |
| cn_head.eval() | |
| # ConvNext Preprocessing (Standard ImageNet) | |
| cn_preprocess = transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.CenterCrop(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) | |
| ]) | |
| # --- 3. FEATURE EXTRACTION (Heuristics) --- | |
| def extract_simple_features(image_path): | |
| img = Image.open(image_path).convert('RGB') | |
| img_array = np.array(img) / 255.0 | |
| edges = np.abs(np.diff(np.mean(img_array, axis=2), axis=0)).mean() + \ | |
| np.abs(np.diff(np.mean(img_array, axis=2), axis=1)).mean() | |
| img_smooth = np.array(img.filter(ImageFilter.GaussianBlur(2))) / 255.0 | |
| noise = np.mean((img_array - img_smooth) ** 2) * 1000 | |
| return { | |
| 'noise_level': noise, | |
| 'edge_density': edges, | |
| 'is_too_clean': (noise < 0.05 and edges < 0.12) # Adjusted thresholds | |
| } | |
| # --- 4. THE ENSEMBLE INFERENCE --- | |
| def run_ensemble(image_path): | |
| img = Image.open(image_path).convert('RGB') | |
| # openclip Score | |
| img_openclip = openclip_preprocess(img).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| sig_feat = openclip_model.encode_image(img_openclip) | |
| sig_feat /= sig_feat.norm(dim=-1, keepdim=True) | |
| sig_logit = openclip_head(sig_feat) | |
| prob_openclip = torch.sigmoid(sig_logit).item() | |
| # ConvNeXt Score | |
| img_cn = cn_preprocess(img).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| feat = cn_backbone.features(img_cn) | |
| feat = cn_backbone.avgpool(feat) | |
| feat = torch.flatten(feat, 1) | |
| cn_logit = cn_head(feat) | |
| prob_cn = torch.sigmoid(cn_logit).item() | |
| # Average the two for the "Raw Ensemble Score" | |
| raw_ensemble_score = (prob_openclip + prob_cn) / 2 | |
| # Calibration | |
| features = extract_simple_features(image_path) | |
| if features['is_too_clean']: | |
| calibrated_score = raw_ensemble_score * 0.55 # 45% discount for product shots | |
| reason = "Clean product-shot detected. Reducing probability." | |
| else: | |
| calibrated_score = raw_ensemble_score | |
| reason = "Standard analysis applied." | |
| return { | |
| 'openclip_score': prob_openclip, | |
| 'convnext_score': prob_cn, | |
| 'raw_ensemble': raw_ensemble_score, | |
| 'calibrated': min(calibrated_score, 0.95), | |
| 'reason': reason, | |
| 'features': features | |
| } | |
| # --- 5. TEST IT --- | |
| test_image = "/Users/rishitbaitule/Downloads/b.jpg" # Update this path! | |
| if os.path.exists(test_image): | |
| results = run_ensemble(test_image) | |
| print("-" * 30) | |
| print(f"Individual openclip: {results['openclip_score']:.2%}") | |
| print(f"Individual ConvNeXt: {results['convnext_score']:.2%}") | |
| print("-" * 30) | |
| print(f"ENSEMBLE RAW SCORE: {results['raw_ensemble']:.2%}") | |
| print(f"CALIBRATED SCORE: {results['calibrated']:.2%}") | |
| print(f"REASON: {results['reason']}") | |
| print("-" * 30) | |
| else: | |
| print("Image not found. Please check test_image path.") |