{"cells":[{"cell_type":"markdown","metadata":{},"source":"# Recod.ai/LUC Scientific Image Forgery Detection - WayneIA Inference V2\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\nWayneIA Position_1 | December 23, 2025 | Year-8 RHINOCEROS G9"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Environment Setup\nimport sys\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(\"=\"*60)\nprint(\"Recod.ai/LUC Forgery Detection - WayneIA Inference V2\")\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}\")\n\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport cv2\nfrom PIL import Image\nfrom tqdm.auto import tqdm\nfrom typing import List, Tuple\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ntry:\n    from transformers import Dinov2Model, AutoImageProcessor\n    print(\"[OK] transformers available\")\nexcept ImportError:\n    print(\"[ERROR] transformers not available\")\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    # Paths\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\")  # Model dataset\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    # Model\n    DINOV2_MODEL = \"facebook/dinov2-base\"\n    FEATURE_LAYERS = [6, 9, 12]\n    \n    # Inference\n    IMG_SIZE = 518  # DINOv2 native size\n    CROP_SIZE = 504  # Multiple of 14\n    BATCH_SIZE = 1\n    THRESHOLD = 0.5\n\nconfig = Config()\nprint(f\"Data dir: {config.DATA_DIR}\")\nprint(f\"Model dir: {config.MODEL_DIR}\")\nprint(f\"Output dir: {config.OUTPUT_DIR}\")\n\n# List available files\nif config.MODEL_DIR.exists():\n    print(f\"\\nModel files: {list(config.MODEL_DIR.glob('*'))}\")\nelse:\n    print(f\"[WARNING] Model dir not found: {config.MODEL_DIR}\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Model Architecture (must match training)\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        \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    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], freeze_backbone=False):\n        super().__init__()\n        self.backbone = Dinov2Model.from_pretrained(model_name)\n        self.processor = AutoImageProcessor.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        \n        spatial_features = []\n        B, _, H, W = x.shape\n        h = H // 14\n        w = W // 14\n        \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        \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\nprint(\"Loading model...\")\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Device: {device}\")\n\nmodel = DualHeadDINOv2(\n    model_name=config.DINOV2_MODEL,\n    feature_layers=config.FEATURE_LAYERS,\n    freeze_backbone=False\n)\n\n# Load trained weights\ncheckpoint_path = config.MODEL_DIR / \"best_model.pth\"\nprint(f\"Loading checkpoint: {checkpoint_path}\")\n\nif checkpoint_path.exists():\n    checkpoint = torch.load(checkpoint_path, map_location=device)\n    model.load_state_dict(checkpoint['model_state_dict'])\n    print(f\"Loaded checkpoint from epoch {checkpoint.get('epoch', 'unknown')}\")\n    print(f\"Best dice score: {checkpoint.get('best_dice', 'unknown')}\")\nelse:\n    print(f\"[ERROR] Checkpoint not found at {checkpoint_path}\")\n    print(\"Available files:\")\n    if config.MODEL_DIR.exists():\n        for f in config.MODEL_DIR.iterdir():\n            print(f\"  {f}\")\n    raise FileNotFoundError(f\"Model checkpoint not found: {checkpoint_path}\")\n\nmodel = model.to(device)\nmodel.eval()\nprint(\"[OK] Model loaded\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Inference Transform\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\ndef preprocess_image(image_path):\n    \"\"\"Load and preprocess image for inference\"\"\"\n    image = cv2.imread(str(image_path))\n    if image is None:\n        # Try PIL as fallback\n        image = np.array(Image.open(image_path).convert('RGB'))\n    else:\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    transformed = inference_transform(image=image)\n    return transformed['image'].unsqueeze(0)  # Add batch dimension\n\nprint(\"[OK] Inference transform defined\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Process Test Images\nprint(\"\\n\" + \"=\"*60)\nprint(\"Processing Test Images\")\nprint(\"=\"*60)\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 = []\n\nfor img_path in tqdm(test_images, desc=\"Inference\"):\n    case_id = img_path.stem  # e.g., \"45\" from \"45.png\"\n    \n    try:\n        # Preprocess\n        image_tensor = preprocess_image(img_path).to(device)\n        \n        # Inference\n        with torch.no_grad():\n            cls_logits, mask_logits = model(image_tensor, return_mask=True)\n            cls_prob = torch.sigmoid(cls_logits).item()\n        \n        # Classify: >0.5 = forged, <0.5 = authentic\n        annotation = \"forged\" if cls_prob > config.THRESHOLD else \"authentic\"\n        \n        predictions.append({\n            'case_id': case_id,\n            'annotation': annotation,\n            'confidence': cls_prob\n        })\n        \n        print(f\"  {case_id}: {annotation} (conf={cls_prob:.4f})\")\n    \n    except Exception as e:\n        print(f\"  [ERROR] {case_id}: {e}\")\n        # Default to authentic on error\n        predictions.append({\n            'case_id': case_id,\n            'annotation': 'authentic',\n            'confidence': 0.0\n        })\n\nprint(f\"\\nProcessed {len(predictions)} images\")"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Generate Submission\nprint(\"\\n\" + \"=\"*60)\nprint(\"Generating Submission\")\nprint(\"=\"*60)\n\nsubmission_df = pd.DataFrame(predictions)[['case_id', 'annotation']]\n\n# Verify format\nprint(f\"Submission shape: {submission_df.shape}\")\nprint(f\"Columns: {submission_df.columns.tolist()}\")\n\n# Distribution\nprint(f\"\\nPrediction distribution:\")\nprint(submission_df['annotation'].value_counts())\n\n# Save\nconfig.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\nsubmission_path = config.OUTPUT_DIR / \"submission.csv\"\nsubmission_df.to_csv(submission_path, index=False)\nprint(f\"\\nSubmission saved to: {submission_path}\")\n\n# Preview\nprint(\"\\nSubmission preview:\")\nprint(submission_df)"},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":"# Final Status\nprint(\"\\n\" + \"=\"*60)\nprint(\"Recod.ai/LUC WayneIA Inference V2 - COMPLETE\")\nprint(\"CODE_KEY[166] FORGERY_CLASSIFIER_MATRIX\")\nprint(\"=\"*60)\nprint(f\"\\nSubmission file: {submission_path}\")\nprint(f\"Total predictions: {len(submission_df)}\")\nprint(f\"\\nTraining metrics (Phase 2):\")\nprint(f\"  - Best Dice: 0.6981\")\nprint(f\"  - Best AUC: 0.9022\")\nprint(f\"  - Best F1: 0.7964\")\nprint(f\"\\nReady for Kaggle submission\")\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}