{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"},{"sourceId":14022773,"sourceType":"datasetVersion","datasetId":8930539},{"sourceId":677234,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":513521,"modelId":528156}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Reproduction of the training setup and dataset split used in the training the model\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split \nfrom sklearn.metrics import precision_recall_fscore_support, confusion_matrix\nimport timm \nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport math \nimport torchvision.transforms.functional as TF\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n\nCONFIG = {\n    \"seed\": 42,\n    \"img_size\": 224,\n    \"batch_size\": 32,                         \n    \"num_classes\": 3,        \n    \"model_name\": \"swin_tiny_patch4_window7_224\", \n    \"device\": torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\"),\n    \"csv_path\": \"/kaggle/input/aptos2019-blindness-detection/train.csv\",\n    \"image_dir\": \"/kaggle/input/preprocessed-images-224\"\n}\n\nLABEL_MAP = {0: 0, 1: 1, 2: 1, 3: 2, 4: 2}\n\ndef seed_everything(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed(seed)\n        torch.backends.cudnn.deterministic = True\nseed_everything(CONFIG['seed'])\n\nclass APTOSFastDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\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_name = row['id_code']\n        img_path = os.path.join(self.img_dir, img_name + \".png\")\n        \n        image = cv2.imread(img_path)\n        if image is None: \n            \n            image = np.zeros((CONFIG['img_size'], CONFIG['img_size'], 3), dtype=np.uint8)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        if self.transform: \n            image = self.transform(image)\n            \n        new_label = LABEL_MAP[row['diagnosis']]\n        return image, torch.tensor(new_label, dtype=torch.long)\n\n\nval_tf = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.ToTensor(),\n    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])\n])\n\n\ntest_tf = val_tf\n\ndf = pd.read_csv(CONFIG['csv_path'])\ndf['new_label'] = df['diagnosis'].map(LABEL_MAP)\n\n\ntrain_val_df, test_df = train_test_split(\n    df, \n    test_size=0.2, \n    random_state=CONFIG['seed'], \n    stratify=df['new_label']\n)\ntest_loader = DataLoader(\n    APTOSFastDataset(test_df, CONFIG['image_dir'], test_tf), \n    batch_size=CONFIG['batch_size'], \n    shuffle=False, \n    num_workers=2\n)\n\n# Completed the reproduction of the training setup and dataset split used in the training the model\n\n# Load trained model\n\nMODEL_PATH = \"/kaggle/input/final-best-sick-f1-model/pytorch/default/1/best_sick_f1_model.pth\"\nmodel = timm.create_model(CONFIG['model_name'], pretrained=False, num_classes=CONFIG['num_classes'])\nmodel.load_state_dict(torch.load(MODEL_PATH, map_location=CONFIG['device']))\nmodel = model.to(CONFIG['device'])\nmodel.eval()\n\n# ====================================================\n# Define function to add Gaussian noise\n# ====================================================\n\nclass AddGaussianNoise:\n    \n    def __init__(self, mean=0., std=0.1):\n        self.std = std\n        self.mean = mean\n        \n    def __call__(self, tensor):\n        # tensor is already normalized to [-1, 1] range\n        noise = torch.randn_like(tensor) * self.std + self.mean\n        noisy_tensor = tensor + noise\n        return noisy_tensor\n    \n    def __repr__(self):\n        return f\"{self.__class__.__name__}(mean={self.mean}, std={self.std})\"\n\n# ====================================================\n# Noise Test Function \n# ====================================================\n\ndef test_noisy(clean_result, noise_levels, model, test_df):\n    model.eval()\n    all_results = []\n\n    print(\"\\n\" + \"=\"*70)\n    print(\"Starting Gaussian Noise Test\")\n    print(\"=\"*70)\n\n    # ====================================================\n    #Test model performance under different Gaussian noise levels\n    # ====================================================\n    \n    for noise_std in noise_levels: \n        # 1. Create transform with noise\n        transform = transforms.Compose([\n            transforms.ToPILImage(),\n            transforms.ToTensor(),\n            \n            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),\n            AddGaussianNoise(std=noise_std) # Add noise after normalization\n        ])\n        \n        # 2. Create DataLoader with noisy transform\n        test_dataset = APTOSFastDataset(test_df, CONFIG['image_dir'], transform)\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CONFIG['batch_size'],\n            shuffle=False,\n            num_workers=2\n        )\n        \n        # 3. Inference on noisy images\n        test_preds, test_labels = [], []\n        with torch.no_grad():\n            for imgs, lbls in tqdm(test_loader, desc=\"Testing...\", leave=True):\n                imgs = imgs.to(CONFIG['device'])\n                outputs = model(imgs)\n                _, p = torch.max(outputs, 1)\n                test_preds.extend(p.cpu().numpy())\n                test_labels.extend(lbls.numpy())\n        \n        # 4. Calculate metrics\n        test_metrics = precision_recall_fscore_support(test_labels, test_preds, \n                                                       labels=[0, 1, 2], zero_division=0)\n        test_precision, test_recall, test_f1_scores = test_metrics[0], test_metrics[1], test_metrics[2]\n        test_avg_sick_f1 = (test_f1_scores[1] + test_f1_scores[2]) / 2.0\n        test_overall_acc = np.mean(np.array(test_preds) == np.array(test_labels))\n        \n        # 5. Print results\n        print(f\"\\nGaussian Noise Level: std={noise_std}\")\n        print(f\"Test Overall Acc: {test_overall_acc:.4f}\")\n        print(\"-\" * 65)\n        print(f\"{'Class':<20} | {'Recall':<10} | {'Precision':<10}  | {'F1-Score':<10}\")\n        print(\"-\" * 65)\n        print(f\"{'0 (Healthy)':<20} | {test_recall[0]:.4f}      | {test_precision[0]:.4f}       | {test_f1_scores[0]:.4f}\")\n        print(f\"{'1 (Mild/Mod)':<20} | {test_recall[1]:.4f}      | {test_precision[1]:.4f}       | {test_f1_scores[1]:.4f}\")\n        print(f\"{'2 (Sev/Prolif)':<20} | {test_recall[2]:.4f}      | {test_precision[2]:.4f}       | {test_f1_scores[2]:.4f}\")\n        print(\"-\" * 65)\n        print(f\"Test Avg Sick F1: {test_avg_sick_f1:.4f}\")\n        \n        # 6. Save results\n        result = {\n            'noise_std': noise_std,\n            'accuracy': test_overall_acc,\n            'precision': test_precision,\n            'recall': test_recall,\n            'f1': test_f1_scores,\n            'avg_sick_f1': test_avg_sick_f1,\n            'preds': test_preds,\n            'labels': test_labels\n        }\n        all_results.append(result)\n        \n        # 7. Plot confusion matrix\n        cm = confusion_matrix(result['labels'], result['preds'])\n        plt.figure(figsize=(8, 6))\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Reds', \n                    xticklabels=['0', '1', '2'], yticklabels=['0', '1', '2'])\n        plt.title(f'Confusion Matrix (Noise std={noise_std})\\nTest Avg Sick F1: {result[\"avg_sick_f1\"]:.4f}')\n        plt.xlabel('Predicted Label')\n        plt.ylabel('True Label')\n        plt.show()\n    \n    # 8. Print summary table\n    print(\"\\n\" + \"=\"*70)\n    print(\"Gaussian Noise Test Results Summary\")\n    print(\"=\"*70)\n    print(f\"{'Noise Std':<15} {'Accuracy':<10} {'F1-Healthy':<12} {'F1-Mild/Mod':<14} {'F1-Sev/Prolif':<15} {'Avg Sick F1':<12}\")\n    print(\"-\" * 80)\n    print(f\"{'0.000':<15} {clean_result['accuracy']:<10.4f} {clean_result['f1'][0]:<12.4f} \"\n          f\"{clean_result['f1'][1]:<14.4f} {clean_result['f1'][2]:<15.4f} {clean_result['avg_sick_f1']:<12.4f}\")\n    for result in all_results:\n        noise_std = result['noise_std']\n        f1_scores = result['f1']\n        print(f\"{noise_std:<15.3f} {result['accuracy']:<10.4f} {f1_scores[0]:<12.4f} \"\n              f\"{f1_scores[1]:<14.4f} {f1_scores[2]:<15.4f} {result['avg_sick_f1']:<12.4f}\")\n    \n    print(\"\\n\" + \"=\"*70)\n    print(\"Gaussian Noise Test Completed!\")\n    print(\"=\"*70)\n    return all_results\n\n\n# ====================================================\n# Define function to add Gaussian blur\n# ====================================================\n\nclass AddGaussianBlur:\n\n   \n    def __init__(self, sigma=0.1, kernel_size=None):\n        if kernel_size is None:\n            # Auto-calculate: kernel_size = 2 * ceil(3 * sigma) + 1\n            self.kernel_size = 2 * math.ceil(3 * sigma) + 1\n            self.kernel_size = max(3, self.kernel_size)  # Minimum 3\n        else:\n            self.kernel_size = kernel_size\n            \n        if self.kernel_size % 2 == 0:\n            self.kernel_size += 1\n        \n        self.sigma = sigma\n\n        \n    def __call__(self, tensor):\n        # tensor shape: [C, H, W], value range: [0, 1]\n        \n        blurred = TF.gaussian_blur(tensor, kernel_size=self.kernel_size, sigma=self.sigma)\n        return blurred\n    \n    def __repr__(self):\n        return f\"{self.__class__.__name__}(sigma={self.sigma}, kernel_size={self.kernel_size})\"\n\n# ====================================================\n# Blur Test Function \n# ====================================================\n\ndef test_blur(clean_result, blur_levels, model, test_df):\n    model.eval()\n    all_results = []\n    print(\"\\n\" + \"=\"*70)\n    print(\"Starting Gaussian Blur Test\")\n    print(\"=\"*70)\n\n    # ====================================================\n    #Test model performance under different Gaussian blur levels\n    # ====================================================\n\n    for sigma in blur_levels: \n        # 1. Create transform with blur\n        transform = transforms.Compose([\n            transforms.ToPILImage(),\n            transforms.ToTensor(),\n            AddGaussianBlur(sigma=sigma),  #Call function to add blur\n            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])\n        ])\n        \n        # 2. Create DataLoader with blur transform\n        test_dataset = APTOSFastDataset(test_df, CONFIG['image_dir'], transform)\n        test_loader = DataLoader(\n            test_dataset,\n            batch_size=CONFIG['batch_size'],\n            shuffle=False,\n            num_workers=2\n        )\n        \n        # 3. Inference on blurred images\n        test_preds, test_labels = [], []\n        with torch.no_grad():\n            for imgs, lbls in tqdm(test_loader, desc=\"Testing...\", leave=True):\n                imgs = imgs.to(CONFIG['device'])\n                outputs = model(imgs)\n                _, p = torch.max(outputs, 1)\n                test_preds.extend(p.cpu().numpy())\n                test_labels.extend(lbls.numpy())\n        \n        # 4. Calculate metrics\n        test_metrics = precision_recall_fscore_support(test_labels, test_preds, \n                                                       labels=[0, 1, 2], zero_division=0)\n        test_precision, test_recall, test_f1_scores = test_metrics[0], test_metrics[1], test_metrics[2]\n        test_avg_sick_f1 = (test_f1_scores[1] + test_f1_scores[2]) / 2.0\n        test_overall_acc = np.mean(np.array(test_preds) == np.array(test_labels))\n        \n        # 5. Print results\n        print(f\"\\nGaussian Blur Level: sigma={sigma}\")\n        print(f\"Test Overall Acc: {test_overall_acc:.4f}\")\n        print(\"-\" * 65)\n        print(f\"{'Class':<20} | {'Recall':<10} | {'Precision':<10}  | {'F1-Score':<10}\")\n        print(\"-\" * 65)\n        print(f\"{'0 (Healthy)':<20} | {test_recall[0]:.4f}      | {test_precision[0]:.4f}       | {test_f1_scores[0]:.4f}\")\n        print(f\"{'1 (Mild/Mod)':<20} | {test_recall[1]:.4f}      | {test_precision[1]:.4f}       | {test_f1_scores[1]:.4f}\")\n        print(f\"{'2 (Sev/Prolif)':<20} | {test_recall[2]:.4f}      | {test_precision[2]:.4f}       | {test_f1_scores[2]:.4f}\")\n        print(\"-\" * 65)\n        print(f\"Test Avg Sick F1: {test_avg_sick_f1:.4f}\")\n        \n        # 6. Save results\n        result = {\n            'sigma': sigma,\n            'accuracy': test_overall_acc,\n            'precision': test_precision,\n            'recall': test_recall,\n            'f1': test_f1_scores,\n            'avg_sick_f1': test_avg_sick_f1,\n            'preds': test_preds,\n            'labels': test_labels\n        }\n        all_results.append(result)\n        \n        # 7. Plot confusion matrix\n        cm = confusion_matrix(result['labels'], result['preds'])\n        plt.figure(figsize=(8, 6))\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n                    xticklabels=['0', '1', '2'], yticklabels=['0', '1', '2'])\n        plt.title(f'Confusion Matrix (Blur sigma={sigma})\\nTest Avg Sick F1: {result[\"avg_sick_f1\"]:.4f}')\n        plt.xlabel('Predicted Label')\n        plt.ylabel('True Label')\n        plt.show()\n    \n    # 8. Print summary table\n    print(\"\\n\" + \"=\"*70)\n    print(\"Gaussian Blur Test Results Summary\")\n    print(\"=\"*70)\n    print(f\"{'Sigma':<15} {'Accuracy':<10} {'F1-Healthy':<12} {'F1-Mild/Mod':<14} {'F1-Sev/Prolif':<15} {'Avg Sick F1':<12}\")\n    print(\"-\" * 80)\n    print(f\"{'0.000':<15} {clean_result['accuracy']:<10.4f} {clean_result['f1'][0]:<12.4f} \"\n          f\"{clean_result['f1'][1]:<14.4f} {clean_result['f1'][2]:<15.4f} {clean_result['avg_sick_f1']:<12.4f}\")\n    for result in all_results:\n        sigma = result['sigma']\n        f1_scores = result['f1']\n        print(f\"{sigma:<15.3f} {result['accuracy']:<10.4f} {f1_scores[0]:<12.4f} \"\n              f\"{f1_scores[1]:<14.4f} {f1_scores[2]:<15.4f} {result['avg_sick_f1']:<12.4f}\")\n    \n    print(\"\\n\" + \"=\"*70)\n    print(\"Gaussian Blur Test Completed!\")\n    print(\"=\"*70)\n    \n    return all_results\n\n# ====================================================\n# Execute Tests\n# ====================================================\n\n# Print system information\nprint(f\"Device: {CONFIG['device']}\")\nprint(f\"Model: {CONFIG['model_name']}\")\nprint(f\"Test samples: {len(test_df)}\")\nprint(f\"Batch size: {CONFIG['batch_size']}\")\n\n# ====================================================\n# Execute Clean Data Tests\n# ====================================================\n\n# Clean data test (baseline)\nprint(\"\\n\" + \"=\"*70)\nprint(\"CLEAN DATA TEST (BASELINE)\")\nprint(\"=\"*70)\n\n# 1. Inference on clean test set\ntest_preds, test_labels = [], []\nwith torch.no_grad():\n    for imgs, lbls in tqdm(test_loader, desc=\"Testing clean data set...\", leave=True):\n        imgs = imgs.to(CONFIG['device'])\n        outputs = model(imgs)\n        _, p = torch.max(outputs, 1)\n        test_preds.extend(p.cpu().numpy())\n        test_labels.extend(lbls.numpy())\n\n# 2. Calculate metrics for clean data\ntest_metrics = precision_recall_fscore_support(test_labels, test_preds, labels=[0, 1, 2], zero_division=0)\ntest_precision, test_recall, test_f1_scores = test_metrics[0], test_metrics[1], test_metrics[2]\ntest_avg_sick_f1 = (test_f1_scores[1] + test_f1_scores[2]) / 2.0\ntest_overall_acc = np.mean(np.array(test_preds) == np.array(test_labels))\n\n# 3. Save clean data test results\nclean_result = {\n    'type': 'clean',\n    'accuracy': test_overall_acc,\n    'precision': test_precision,\n    'recall': test_recall,\n    'f1': test_f1_scores,\n    'avg_sick_f1': test_avg_sick_f1,\n    'preds': test_preds,\n    'labels': test_labels\n}\n\n# 4. Print clean data test report\nprint(f\"\\nClean Data Test\")\nprint(f\"Test Overall Acc: {test_overall_acc:.4f}\")\nprint(\"-\" * 65)\nprint(f\"{'Class':<20} | {'Recall':<10} | {'Precision':<10} | {'F1-Score':<10}\")\nprint(\"-\" * 65)\nprint(f\"{'0 (Healthy)':<20} | {test_recall[0]:.4f}      | {test_precision[0]:.4f}         | {test_f1_scores[0]:.4f}\")\nprint(f\"{'1 (Mild/Mod)':<20} | {test_recall[1]:.4f}      | {test_precision[1]:.4f}         | {test_f1_scores[1]:.4f}\")\nprint(f\"{'2 (Sev/Prolif)':<20} | {test_recall[2]:.4f}      | {test_precision[2]:.4f}         | {test_f1_scores[2]:.4f}\")\nprint(\"-\" * 65)\nprint(f\"Test Avg Sick F1: {test_avg_sick_f1:.4f}\")\nprint(\"#\"*65)\n\n# 5. Plot confusion matrix for clean data tset\ncm = confusion_matrix(test_labels, test_preds)\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Greens', \n            xticklabels=['0', '1', '2'], yticklabels=['0', '1', '2'])\nplt.title(f'Confusion Matrix (Clean Data)\\nTest Avg Sick F1: {test_avg_sick_f1:.4f}')\nplt.xlabel('Predicted Label')\nplt.ylabel('True Label')\nplt.show()\n\n\n# ====================================================\n# Execute Robustness Tests\n# ====================================================\n\n\n# Define the noise level we want to test\nnoise_levels=[0.01, 0.03, 0.05, 0.07, 0.10]\n# Execute the Gaussian noise tset\nall_noise_results = test_noisy(clean_result, noise_levels, model, test_df)\n\n# Define the blur level we want to test\nblur_levels = [0.1, 0.2, 0.4, 0.6, 0.8]\n# Execute the Gaussian blur tset\nall_blur_results = test_blur(clean_result, blur_levels, model, test_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-10T08:05:32.287982Z","iopub.execute_input":"2025-12-10T08:05:32.288381Z","iopub.status.idle":"2025-12-10T08:27:10.471408Z","shell.execute_reply.started":"2025-12-10T08:05:32.288348Z","shell.execute_reply":"2025-12-10T08:27:10.470283Z"}},"outputs":[],"execution_count":null}]}