{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "# Plant Pathology 2021 - EfficientNet-B0 Inference\n\nInference-only notebook that loads pre-trained models and generates predictions.\nModels trained locally with CV=0.9068.\n\nPaths:\n- Competition: /kaggle/input/competitions/plant-pathology-2021-fgvc8/\n- Models: /kaggle/input/datasets/iliamireskandari/plant-pathology-b0-models/"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "import os\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport cv2\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(f'PyTorch: {torch.__version__}')\nprint(f'CUDA: {torch.cuda.is_available()}')\nif torch.cuda.is_available():\n    print(f'GPU: {torch.cuda.get_device_name(0)}')\n    print(f'Compute capability: {torch.cuda.get_device_capability(0)}')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Paths\nCOMP_DIR = Path('/kaggle/input/competitions/plant-pathology-2021-fgvc8')\nTEST_DIR = COMP_DIR / 'test_images'\nOUTPUT_DIR = Path('/kaggle/working')\n\nprint(f'Competition dir exists: {COMP_DIR.exists()}')\nprint(f'Test dir exists: {TEST_DIR.exists()}')\n\n# Config\nCLASSES = ['complex', 'frog_eye_leaf_spot', 'healthy', 'powdery_mildew', 'rust', 'scab']\nNUM_CLASSES = len(CLASSES)\nIMG_SIZE = 256\nBATCH_SIZE = 32\nNUM_FOLDS = 5\nMODEL_NAME = 'efficientnet_b0'\n\n# Per-class thresholds from OOF analysis\nPER_CLASS_THRESHOLDS = {\n    'complex': 0.34,\n    'frog_eye_leaf_spot': 0.44,\n    'healthy': 0.70,\n    'powdery_mildew': 0.60,\n    'rust': 0.50,\n    'scab': 0.40\n}\nTHRESH_ARRAY = np.array([PER_CLASS_THRESHOLDS[c] for c in CLASSES])\n\n# Use CPU to avoid CUDA compatibility issues with P100\nDEVICE = 'cpu'\nprint(f'Device: {DEVICE}')\n\n# Find models dataset - search all possible paths\nimport glob as glob_module\n\n# Search for fold1_model.pth anywhere in /kaggle/input\nall_pth = glob_module.glob('/kaggle/input/**/*.pth', recursive=True)\nprint(f'All .pth files found: {all_pth}')\n\nif all_pth:\n    MODELS_DIR = Path(all_pth[0]).parent\n    print(f'Found models at: {MODELS_DIR}')\nelse:\n    MODELS_DIR = Path('/kaggle/input/plant-pathology-b0-models')\n    print(f'Using default path: {MODELS_DIR}')\n\nprint(f'Models dir exists: {MODELS_DIR.exists()}')\nif MODELS_DIR.exists():\n    print(f'Model files: {list(MODELS_DIR.glob(\"*.pth\"))}')\n"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Dataset for inference\nclass PlantDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = self.img_dir / row['image']\n        img = cv2.imread(str(img_path))\n        if img is None:\n            img = np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        if self.transform:\n            augmented = self.transform(image=img)\n            img = augmented['image']\n        \n        return img\n\ndef get_transform(img_size, hflip=False, vflip=False):\n    transforms = [A.Resize(img_size, img_size)]\n    if hflip:\n        transforms.append(A.HorizontalFlip(p=1.0))\n    if vflip:\n        transforms.append(A.VerticalFlip(p=1.0))\n    transforms.extend([\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ])\n    return A.Compose(transforms)\n\n# Model\nclass PlantModel(nn.Module):\n    def __init__(self, model_name, num_classes):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=False, num_classes=num_classes)\n        \n    def forward(self, x):\n        return self.model(x)\n\nprint('Dataset and model defined')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Generate test predictions with TTA\nprint('=== Generating Test Predictions with TTA ===')\n\ntest_images = sorted(os.listdir(TEST_DIR))\nprint(f'Test images: {len(test_images)}')\n\ntest_df = pd.DataFrame({'image': test_images})\n\n# TTA configurations\ntta_configs = [\n    {'hflip': False, 'vflip': False},  # Original\n    {'hflip': True, 'vflip': False},   # H-flip\n    {'hflip': False, 'vflip': True},   # V-flip\n    {'hflip': True, 'vflip': True},    # H+V flip\n]\n\ntest_preds_all = np.zeros((len(test_df), NUM_CLASSES))\n\nfor fold in range(NUM_FOLDS):\n    model_path = MODELS_DIR / f'fold{fold+1}_model.pth'\n    if not model_path.exists():\n        print(f'Model not found: {model_path}')\n        continue\n    \n    model = PlantModel(MODEL_NAME, NUM_CLASSES).to(DEVICE)\n    state_dict = torch.load(model_path, map_location=DEVICE)\n    model.load_state_dict(state_dict)\n    model.eval()\n    \n    fold_preds = np.zeros((len(test_df), NUM_CLASSES))\n    \n    for tta_cfg in tta_configs:\n        transform = get_transform(IMG_SIZE, **tta_cfg)\n        test_dataset = PlantDataset(test_df, TEST_DIR, transform=transform)\n        test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False,\n                                 num_workers=2, pin_memory=True)\n        \n        tta_preds = []\n        with torch.no_grad():\n            for imgs in test_loader:\n                imgs = imgs.to(DEVICE)\n                outputs = model(imgs)\n                preds = torch.sigmoid(outputs).cpu().numpy()\n                tta_preds.append(preds)\n        \n        fold_preds += np.vstack(tta_preds) / len(tta_configs)\n    \n    test_preds_all += fold_preds / NUM_FOLDS\n    print(f'Fold {fold+1} predictions done')\n    \n    del model\n    torch.cuda.empty_cache() if DEVICE == 'cuda' else None\n\nprint(f'Test predictions shape: {test_preds_all.shape}')\nprint(f'Predictions range: [{test_preds_all.min():.3f}, {test_preds_all.max():.3f}]')"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": "# Create submission\ndef preds_to_labels(preds, thresholds, classes):\n    results = []\n    for pred in preds:\n        binary = (pred >= thresholds).astype(int)\n        if binary.sum() == 0:\n            binary[pred.argmax()] = 1\n        labels = [classes[i] for i in range(len(classes)) if binary[i] == 1]\n        results.append(' '.join(labels))\n    return results\n\nsubmission_labels = preds_to_labels(test_preds_all, THRESH_ARRAY, CLASSES)\n\nsubmission = pd.DataFrame({\n    'image': test_df['image'].values,\n    'labels': submission_labels\n})\n\nprint('Submission preview:')\nprint(submission)\n\nsubmission.to_csv(OUTPUT_DIR / 'submission.csv', index=False)\nprint(f'\\nSubmission saved to {OUTPUT_DIR}/submission.csv')\nprint(f'CV Mean F1 (from training): 0.9068')"}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10.0"}}, "nbformat": 4, "nbformat_minor": 4}