{"cells":[{"cell_type":"markdown","metadata":{},"source":"# Recod.ai/LUC Scientific Image Forgery Detection - WayneIA V4\n\n**Competition**: recodai-luc-scientific-image-forgery-detection  \n**Prize**: $55,000  \n**Model**: DINOv2 + Dual-Head (Classification + Segmentation)  \n**Training Metrics**: Dice=0.6981, AUC=0.9022, F1=0.7964\n\nCODE_KEY[166] FORGERY_CLASSIFIER_MATRIX\n\nV4 Fix: Protobuf compatibility + correct architecture\n\nWayneIA Position_1 OpusPlan | December 23, 2025 | Year-8 RHINOCEROS G9"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# CRITICAL: Set protobuf implementation BEFORE any imports\nimport os\nos.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python'\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\n\nimport sys\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"=\"*60)\nprint(\"Recod.ai/LUC Forgery Detection - WayneIA V4\")\nprint(\"CODE_KEY[166] FORGERY_CLASSIFIER_MATRIX\")\nprint(\"=\"*60)\n\nIN_KAGGLE = 'kaggle_web_client' in sys.modules or os.path.exists('/kaggle/input')\nprint(f\"Kaggle environment: {IN_KAGGLE}\")\nprint(f\"Python version: {sys.version}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Core imports\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport cv2\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom transformers import Dinov2Model, AutoImageProcessor\n\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\nprint(\"[OK] All imports successful\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Configuration\nclass Config:\n    if IN_KAGGLE:\n        DATA_DIR = Path(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection\")\n        MODEL_DIR = Path(\"/kaggle/input/recod-wayneia-model\")\n        OUTPUT_DIR = Path(\"/kaggle/working\")\n    else:\n        DATA_DIR = Path(\"/mnt/wayne/competitions/recod_ai_luc/extracted\")\n        MODEL_DIR = Path(\"/mnt/wayne/competitions/recod_ai_luc/experiments/phase2_20251222_192821\")\n        OUTPUT_DIR = Path(\"/mnt/wayne/competitions/recod_ai_luc/output\")\n    \n    DINOV2_MODEL = \"facebook/dinov2-base\"\n    FEATURE_LAYERS = [6, 9, 12]\n    IMG_SIZE = 518\n    CROP_SIZE = 504\n    THRESHOLD = 0.5\n\nconfig = Config()\nprint(f\"Data dir: {config.DATA_DIR}\")\nprint(f\"Model dir: {config.MODEL_DIR}\")\nprint(f\"Model files: {list(config.MODEL_DIR.glob('*'))}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Model Architecture - MUST MATCH TRAINING EXACTLY\n# ClassificationHead uses fc1/fc2 (NOT nn.Sequential)\n\nclass SegmentationHead(nn.Module):\n    def __init__(self, feature_dim=768, decoder_dim=256):\n        super().__init__()\n        self.lateral1 = nn.Conv2d(feature_dim, decoder_dim, kernel_size=1)\n        self.lateral2 = nn.Conv2d(feature_dim, decoder_dim, kernel_size=1)\n        self.lateral3 = nn.Conv2d(feature_dim, decoder_dim, kernel_size=1)\n        self.upsample1 = nn.Sequential(\n            nn.ConvTranspose2d(decoder_dim, decoder_dim, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(decoder_dim), nn.ReLU(inplace=True))\n        self.upsample2 = nn.Sequential(\n            nn.ConvTranspose2d(decoder_dim, decoder_dim, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(decoder_dim), nn.ReLU(inplace=True))\n        self.upsample3 = nn.Sequential(\n            nn.ConvTranspose2d(decoder_dim, decoder_dim, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(decoder_dim), nn.ReLU(inplace=True))\n        self.upsample4 = nn.Sequential(\n            nn.ConvTranspose2d(decoder_dim, decoder_dim, kernel_size=4, stride=2, padding=1),\n            nn.BatchNorm2d(decoder_dim), nn.ReLU(inplace=True))\n        self.final_conv = nn.Conv2d(decoder_dim, 1, kernel_size=1)\n\n    def forward(self, features):\n        f1, f2, f3 = features\n        p3 = self.lateral3(f3)\n        p2 = self.lateral2(f2)\n        p1 = self.lateral1(f1)\n        p3 = self.upsample1(p3)\n        p2 = p2 + F.interpolate(p3, size=p2.shape[2:], mode='bilinear', align_corners=False)\n        p2 = self.upsample2(p2)\n        p1 = p1 + F.interpolate(p2, size=p1.shape[2:], mode='bilinear', align_corners=False)\n        x = self.upsample3(p1)\n        x = self.upsample4(x)\n        return self.final_conv(x)\n\n\nclass ClassificationHead(nn.Module):\n    \"\"\"CRITICAL: Must use fc1/fc2 to match training state_dict keys\"\"\"\n    def __init__(self, feature_dim=768, hidden_dim=256, dropout=0.3):\n        super().__init__()\n        self.fc1 = nn.Linear(feature_dim, hidden_dim)\n        self.relu = nn.ReLU(inplace=True)\n        self.dropout = nn.Dropout(dropout)\n        self.fc2 = nn.Linear(hidden_dim, 1)\n\n    def forward(self, cls_token):\n        x = self.fc1(cls_token)\n        x = self.relu(x)\n        x = self.dropout(x)\n        return self.fc2(x)\n\n\nclass DualHeadDINOv2(nn.Module):\n    def __init__(self, model_name=\"facebook/dinov2-base\", feature_layers=[6, 9, 12]):\n        super().__init__()\n        self.backbone = Dinov2Model.from_pretrained(model_name)\n        self.feature_layers = feature_layers\n        self.segmentation_head = SegmentationHead(feature_dim=768, decoder_dim=256)\n        self.classification_head = ClassificationHead(feature_dim=768, hidden_dim=256, dropout=0.3)\n\n    def extract_features(self, x):\n        outputs = self.backbone(x, output_hidden_states=True)\n        hidden_states = outputs.hidden_states\n        cls_token = hidden_states[-1][:, 0, :]\n        spatial_features = []\n        B, _, H, W = x.shape\n        h, w = H // 14, W // 14\n        for layer_idx in self.feature_layers:\n            hidden = hidden_states[layer_idx][:, 1:, :]\n            feat = hidden.reshape(B, h, w, -1).permute(0, 3, 1, 2)\n            spatial_features.append(feat)\n        return cls_token, spatial_features\n\n    def forward(self, x, return_mask=True):\n        cls_token, spatial_features = self.extract_features(x)\n        cls_logits = self.classification_head(cls_token)\n        if return_mask:\n            mask_logits = self.segmentation_head(spatial_features)\n            mask_logits = F.interpolate(mask_logits, size=(x.shape[2], x.shape[3]), mode='bilinear', align_corners=False)\n            return cls_logits, mask_logits\n        return cls_logits, None\n\nprint(\"[OK] Model architecture defined\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Load Model\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\nmodel = DualHeadDINOv2(config.DINOV2_MODEL, config.FEATURE_LAYERS)\ncheckpoint = torch.load(config.MODEL_DIR / \"best_model.pth\", map_location=device)\nmodel.load_state_dict(checkpoint['model_state_dict'])\nprint(f\"Loaded checkpoint from epoch {checkpoint.get('epoch', 'unknown')}\")\nmodel = model.to(device).eval()\nprint(\"[OK] Model loaded\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Inference\ninference_transform = A.Compose([\n    A.SmallestMaxSize(max_size=config.IMG_SIZE),\n    A.CenterCrop(height=config.CROP_SIZE, width=config.CROP_SIZE),\n    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ToTensorV2(),\n])\n\ntest_images_dir = config.DATA_DIR / \"test_images\"\ntest_images = sorted(list(test_images_dir.glob(\"*.png\")) + list(test_images_dir.glob(\"*.jpg\")))\nprint(f\"Found {len(test_images)} test images\")\n\npredictions = []\nfor img_path in test_images:\n    case_id = img_path.stem\n    image = cv2.imread(str(img_path))\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    tensor = inference_transform(image=image)['image'].unsqueeze(0).to(device)\n    \n    with torch.no_grad():\n        cls_logits, _ = model(tensor, return_mask=False)\n        prob = torch.sigmoid(cls_logits).item()\n    \n    annotation = \"forged\" if prob > config.THRESHOLD else \"authentic\"\n    predictions.append({'case_id': case_id, 'annotation': annotation})\n    print(f\"  {case_id}: {annotation} (conf={prob:.4f})\")\n\nprint(f\"Processed {len(predictions)} images\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Generate Submission\nsubmission_df = pd.DataFrame(predictions)\nconfig.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\nsubmission_path = config.OUTPUT_DIR / \"submission.csv\"\nsubmission_df.to_csv(submission_path, index=False)\n\nprint(f\"\\nSubmission saved to: {submission_path}\")\nprint(f\"\\nSubmission contents:\")\nprint(submission_df)\nprint(\"\\n\" + \"=\"*60)\nprint(\"Recod.ai/LUC WayneIA V4 - COMPLETE\")\nprint(\"CODE_KEY[166] FORGERY_CLASSIFIER_MATRIX\")\nprint(\"=\"*60)\nprint(\"\\nWayneIA: The AND is the AGI\")"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.0"}},"nbformat":4,"nbformat_minor":4}