{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":24800,"databundleVersionId":1831594,"sourceType":"competition"},{"sourceId":12121793,"sourceType":"datasetVersion","datasetId":7632744},{"sourceId":12334731,"sourceType":"datasetVersion","datasetId":7775531},{"sourceId":12425246,"sourceType":"datasetVersion","datasetId":7837104},{"sourceId":12429086,"sourceType":"datasetVersion","datasetId":7839771},{"sourceId":442998,"sourceType":"modelInstanceVersion","modelInstanceId":358580,"modelId":379895},{"sourceId":466652,"sourceType":"modelInstanceVersion","modelInstanceId":376481,"modelId":397159}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q segmentation-models-pytorch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:38:41.188461Z","iopub.execute_input":"2025-07-10T09:38:41.188753Z","iopub.status.idle":"2025-07-10T09:38:44.503692Z","shell.execute_reply.started":"2025-07-10T09:38:41.188731Z","shell.execute_reply":"2025-07-10T09:38:44.502845Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === 1. Imports ===\nimport os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nfrom torchvision import transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models import resnet101, densenet121\nfrom sklearn.utils import resample\nfrom sklearn.model_selection import train_test_split\nimport segmentation_models_pytorch as smp\n\n\n# === 2. Device ===\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n\n# # === 3. Load & Prepare CSV ===\n# df = pd.read_csv('/kaggle/input/our-pacs-data-yolo-640/filtered_data_640.csv')\n# df_grouped = df.groupby('Scan ID')['Disease'].apply(lambda x: '|'.join(sorted(set(x)))).reset_index()\n# df_v1 = df_grouped.copy()\n# df_v1['label'] = df_v1['Disease'].apply(lambda x: 0 if x == 'No Finding' else 1)\n\n\n# def downsample_df(df, label_col='label'):\n#     df_normal = df[df[label_col] == 0]\n#     df_abnormal = df[df[label_col] == 1]\n#     df_normal_downsampled = resample(df_normal, replace=False, n_samples=len(df_abnormal), random_state=42)\n#     df_balanced = pd.concat([df_normal_downsampled, df_abnormal])\n#     return df_balanced.sample(frac=1, random_state=42).reset_index(drop=True)\n\n\n# def split_and_report(df_balanced):\n#     train_df, test_df = train_test_split(df_balanced, test_size=0.2, random_state=42, stratify=df_balanced['label'])\n#     return train_df, test_df\n\n\n# # === 4. Split data ===\n# df_v1_balanced = downsample_df(df_v1)\nv1_train = pd.read_csv(\"/kaggle/input/our-data-ensemble/v1_train_total_balanced.csv\")\nv1_test  = pd.read_csv(\"/kaggle/input/our-data-ensemble/v1_test_total_balanced.csv\")\n\n\n# === 5. Transforms ===\nIMG_SIZE = 256\nval_transform_chex = transforms.Compose([\n    transforms.Lambda(lambda img: img.convert(\"RGB\")),\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n])\n\nval_transform_seg = transforms.Compose([\n    transforms.Resize((512, 512)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n])\n\n\n# === 6. Load segmentation model ===\ndef load_segmentation_model(model_path):\n    model = smp.Unet(encoder_name='resnet34', in_channels=3, classes=1)\n    model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n    model.eval()\n    return model.to(DEVICE)\n\nSEG_MODEL_PATH = \"/kaggle/input/x-ray-segmention-model/xray_Segmention_model.pth\"\nunet_model = load_segmentation_model(SEG_MODEL_PATH)\n\n\n# === 7. Custom Dataset for Ensemble ===\nclass EnsembleDataset(Dataset):\n    def __init__(self, df, image_dir, transform_chexnet, transform_segmentation, seg_model):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n        self.transform_chexnet = transform_chexnet\n        self.transform_seg = transform_segmentation\n        self.seg_model = seg_model\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image_id = row[\"Scan ID\"]\n        label = row[\"label\"]\n        image_path = os.path.join(self.image_dir, f\"{image_id}.png\")\n\n        image = Image.open(image_path).convert(\"RGB\")\n        img_chexnet = self.transform_chexnet(image)\n\n        # 1. Segment image\n        img_for_seg = self.transform_seg(image).unsqueeze(0).to(DEVICE)\n        with torch.no_grad():\n            output = self.seg_model(img_for_seg)\n            mask = torch.sigmoid(output).squeeze().cpu().numpy()\n            mask = (mask > 0.5).astype(np.float32)\n        \n        # 2. Resize image to match mask\n        image_resized = image.resize((512, 512))\n        image_np = np.array(image_resized).astype(np.float32) / 255.0\n        \n        # 3. Apply mask\n        masked_image = image_np * np.expand_dims(mask, axis=-1)\n        \n        # 4. Back to PIL for transform\n        masked_image_pil = Image.fromarray((masked_image * 255).astype(np.uint8))\n        img_segmented = self.transform_chexnet(masked_image_pil)\n\n        return {\n            \"chexnet_img\": img_chexnet,\n            \"chexnet2_img\": img_chexnet,\n            \"seg_img\": img_segmented,\n            \"label\": torch.tensor(label, dtype=torch.float32)\n        }\n\n\n# === 8. Load Models ===\ndef get_resnet101_model():\n    model = resnet101(weights=None)\n    model.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(2048, 2))\n    return model.to(DEVICE)\n\ndef get_chexnet_model(num_classes=2):\n    model = densenet121(weights=None)\n    model.classifier = nn.Linear(1024, num_classes)\n    return model.to(DEVICE)\n\n# Load models\nmodel_loaded_seg = get_resnet101_model()\nmodel_loaded_seg.load_state_dict(torch.load(\"/kaggle/input/models_normal_abnormal/pytorch/default/1/ResNet_Segmentation.pth\", map_location=DEVICE))\n\nmodel_loaded_chexnet_all = get_chexnet_model()\nmodel_loaded_chexnet_all.load_state_dict(torch.load(\"/kaggle/input/models_normal_abnormal/pytorch/default/1/ChexNet_Full.pth\", map_location=DEVICE))\n\nmodel_loaded_chexnet_2_classes = get_chexnet_model()\nmodel_loaded_chexnet_2_classes.load_state_dict(torch.load(\"/kaggle/input/models_normal_abnormal/pytorch/default/1/ChexNet_2classes.pth\", map_location=DEVICE))\n\n\n# === 9. Ensemble Dataloader ===\nIMAGE_DIR = \"/kaggle/input/our-pacs-data-yolo-640/Images/Images\"\nensemble_dataset = EnsembleDataset(\n    df=v1_test,\n    image_dir=IMAGE_DIR,\n    transform_chexnet=val_transform_chex,\n    transform_segmentation=val_transform_seg,\n    seg_model=unet_model\n)\nensemble_loader = DataLoader(ensemble_dataset, batch_size=32, shuffle=False, num_workers=0)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:38:44.505461Z","iopub.execute_input":"2025-07-10T09:38:44.505721Z","iopub.status.idle":"2025-07-10T09:38:52.73626Z","shell.execute_reply.started":"2025-07-10T09:38:44.505697Z","shell.execute_reply":"2025-07-10T09:38:52.735395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def soft_voting_ensemble(models_info, dataloader, device, weights=None):\n    \"\"\"\n    models_info: قائمة تحتوي على tuples بالشكل التالي:\n        [(model_object, model_name_string), ...]\n    \n    dataloader: يجب أن يُرجع (image, label, model_name)\n    \"\"\"\n    all_probs = []\n\n    for model, model_name in models_info:\n        model.eval()\n        model.to(device)\n        probs = []\n\n        with torch.no_grad():\n            for batch in dataloader:\n                image = batch[\"image\"]\n # فقط الصورة\n                image = image.to(device)\n            \n                outputs = model(image)\n                prob = torch.softmax(outputs, dim=1)[:, 1]  # احتمال abnormal\n                probs.extend(prob.cpu().numpy())\n\n        all_probs.append(np.array(probs))\n\n    all_probs = np.stack(all_probs, axis=0)  # الشكل: (n_models, n_samples)\n\n    if weights is None:\n        weights = np.ones(len(models_info)) / len(models_info)\n\n    final_probs = np.average(all_probs, axis=0, weights=weights)\n    return final_probs\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:38:52.737188Z","iopub.execute_input":"2025-07-10T09:38:52.737439Z","iopub.status.idle":"2025-07-10T09:38:52.743431Z","shell.execute_reply.started":"2025-07-10T09:38:52.737421Z","shell.execute_reply":"2025-07-10T09:38:52.742661Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset بدون transform (سيتم تطبيقها داخل الـ ensemble function)\nclass RawLungDataset(Dataset):\n    def __init__(self, df, image_dir):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image_id = row[\"Scan ID\"]\n        label = row[\"label\"]\n        image_path = os.path.join(ORIGINAL_IMAGE_DIR, f\"{image_id}.png\")\n        image = Image.open(image_path).convert(\"RGB\")\n        return image, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:38:52.745315Z","iopub.execute_input":"2025-07-10T09:38:52.745575Z","iopub.status.idle":"2025-07-10T09:38:52.769646Z","shell.execute_reply.started":"2025-07-10T09:38:52.745558Z","shell.execute_reply":"2025-07-10T09:38:52.769021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nfrom torchvision import transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models import resnet101, densenet121\nfrom sklearn.utils import resample\nfrom sklearn.model_selection import train_test_split\nimport segmentation_models_pytorch as smp\nfrom sklearn.metrics import f1_score, precision_score, recall_score, confusion_matrix, ConfusionMatrixDisplay\nimport matplotlib.pyplot as plt\n\n# === 1. Device ===\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# === 2. Load & Prepare CSV ===\ndf = pd.read_csv('/kaggle/input/our-pacs-data-yolo-640/filtered_data_640.csv')\ndf_grouped = df.groupby('Scan ID')['Disease'].apply(lambda x: '|'.join(sorted(set(x)))).reset_index()\ndf_grouped['label'] = df_grouped['Disease'].apply(lambda x: 0 if x == 'No Finding' else 1)\n\ndef downsample_df(df, label_col='label'):\n    df_normal = df[df[label_col] == 0]\n    df_abnormal = df[df[label_col] == 1]\n    df_normal_downsampled = resample(df_normal, replace=False, n_samples=len(df_abnormal), random_state=42)\n    df_balanced = pd.concat([df_normal_downsampled, df_abnormal])\n    return df_balanced.sample(frac=1, random_state=42).reset_index(drop=True)\n\ndef split_and_report(df_balanced):\n    return train_test_split(df_balanced, test_size=0.2, random_state=42, stratify=df_balanced['label'])\n\ndf_balanced = downsample_df(df_grouped)\nv1_train, v1_test = split_and_report(df_balanced)\n\n# === 3. Transforms ===\nIMG_SIZE = 256\nval_transform_chex = transforms.Compose([\n    transforms.Lambda(lambda img: img.convert(\"RGB\")),\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n])\n\nval_transform_seg = transforms.Compose([\n    transforms.Resize((512, 512)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n])\n\n# === 4. Load segmentation model ===\ndef load_segmentation_model(model_path):\n    model = smp.Unet(encoder_name='resnet34', in_channels=3, classes=1)\n    model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n    model.eval()\n    return model.to(DEVICE)\n\nunet_model = load_segmentation_model(\"/kaggle/input/x-ray-segmention-model/xray_Segmention_model.pth\")\n\n# === 5. Custom Dataset ===\nclass EnsembleDataset(Dataset):\n    def __init__(self, df, image_dir, transform_chexnet, transform_segmentation, seg_model):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n        self.transform_chexnet = transform_chexnet\n        self.transform_seg = transform_segmentation\n        self.seg_model = seg_model\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image_id = row[\"Scan ID\"]\n        label = row[\"label\"]\n        image_path = os.path.join(self.image_dir, f\"{image_id}.png\")\n\n        image = Image.open(image_path).convert(\"RGB\")\n        img_chexnet = self.transform_chexnet(image)\n\n        # segmentation\n        img_for_seg = self.transform_seg(image).unsqueeze(0).to(DEVICE)\n        with torch.no_grad():\n            output = self.seg_model(img_for_seg)\n            mask = torch.sigmoid(output).squeeze().cpu().numpy()\n            mask = (mask > 0.5).astype(np.float32)\n\n        image_resized = image.resize((512, 512))\n        image_np = np.array(image_resized).astype(np.float32) / 255.0\n        masked_image = image_np * np.expand_dims(mask, axis=-1)\n        masked_image_pil = Image.fromarray((masked_image * 255).astype(np.uint8))\n        img_segmented = self.transform_chexnet(masked_image_pil)\n\n        return {\n            \"chexnet_img\": img_chexnet,\n            \"chexnet2_img\": img_chexnet,\n            \"seg_img\": img_segmented,\n            \"label\": torch.tensor(label, dtype=torch.float32)\n        }\n\n# === 6. Load Classifier Models ===\ndef get_resnet101_model():\n    model = resnet101(weights=None)\n    model.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(2048, 2))\n    return model.to(DEVICE)\n\ndef get_chexnet_model(num_classes=2):\n    model = densenet121(weights=None)\n    model.classifier = nn.Linear(1024, num_classes)\n    return model.to(DEVICE)\n\nmodel_loaded_seg = get_resnet101_model()\nmodel_loaded_seg.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/best_fc_only_resnet101_epoch10_Segmentation.pth\", map_location=DEVICE))\n\nmodel_loaded_chexnet_all = get_chexnet_model()\nmodel_loaded_chexnet_all.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch7.pth\", map_location=DEVICE))\n\nmodel_loaded_chexnet_2_classes = get_chexnet_model()\nmodel_loaded_chexnet_2_classes.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch9_2classes.pth\", map_location=DEVICE))\n\n# === 7. Ensemble Loader ===\nIMAGE_DIR = \"/kaggle/input/our-pacs-data-yolo-640/Images/Images\"\nensemble_dataset = EnsembleDataset(\n    df=v1_test,\n    image_dir=IMAGE_DIR,\n    transform_chexnet=val_transform_chex,\n    transform_segmentation=val_transform_seg,\n    seg_model=unet_model\n)\nensemble_loader = DataLoader(ensemble_dataset, batch_size=1, shuffle=False, num_workers=0)\n\n# === 8. Ensemble Prediction ===\ndef soft_voting_ensemble(models_info, dataloader, device, weights=None):\n    all_probs = []\n\n    for model, input_key in models_info:\n        model.eval()\n        model.to(device)\n        probs = []\n\n        with torch.no_grad():\n            for batch in dataloader:\n                image = batch[input_key].to(device)\n                output = model(image)\n                prob = torch.softmax(output, dim=1)[:, 1]\n                probs.append(prob.cpu().numpy())\n\n        all_probs.append(np.concatenate(probs))\n\n    all_probs = np.stack(all_probs, axis=0)\n    if weights is None:\n        weights = np.ones(len(models_info)) / len(models_info)\n\n    final_probs = np.average(all_probs, axis=0, weights=weights)\n    return final_probs\n\nmodels_info = [\n    (model_loaded_seg, \"seg_img\"),\n    (model_loaded_chexnet_all, \"chexnet_img\"),\n    (model_loaded_chexnet_2_classes, \"chexnet2_img\")\n]\n\n# Run ensemble\nensemble_probs = soft_voting_ensemble(models_info, ensemble_loader, DEVICE)\nfinal_preds = [1 if p >= 0.5 else 0 for p in ensemble_probs]\ntrue_labels = [sample[\"label\"].item() for sample in ensemble_dataset]\n\n# === 9. Evaluation ===\nf1 = f1_score(true_labels, final_preds)\nprecision = precision_score(true_labels, final_preds)\nrecall = recall_score(true_labels, final_preds)\ncm = confusion_matrix(true_labels, final_preds)\n\nprint(f\"✅ Ensemble F1: {f1:.4f} | Precision: {precision:.4f} | Recall: {recall:.4f}\")\nprint(\"Confusion Matrix:\\n\", cm)\n\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=[\"Normal\", \"Abnormal\"])\ndisp.plot(cmap=plt.cm.Blues)\nplt.title(\"Confusion Matrix - Ensemble\")\nplt.grid(False)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:38:52.770406Z","iopub.execute_input":"2025-07-10T09:38:52.770697Z","iopub.status.idle":"2025-07-10T09:40:05.230676Z","shell.execute_reply.started":"2025-07-10T09:38:52.770675Z","shell.execute_reply":"2025-07-10T09:40:05.229934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix, classification_report, ConfusionMatrixDisplay\n\n# === Step 1: Get model predictions (probabilities) and true labels ===\n# Assumes: ensemble_probs → list of predicted probs (floats)\n#          v1_test[\"label\"].tolist() → list of true labels\ny_scores = ensemble_probs  # output from soft_voting_ensemble\ny_true = v1_test[\"label\"].tolist()\n\n# === Step 2: Try thresholds from 0.0 to 1.0 ===\nthresholds = np.linspace(0, 1, 101)\nprecisions, recalls, f1s = [], [], []\n\nfor t in thresholds:\n    preds = [1 if p >= t else 0 for p in y_scores]\n    precisions.append(precision_score(y_true, preds, zero_division=0))\n    recalls.append(recall_score(y_true, preds, zero_division=0))\n    f1s.append(f1_score(y_true, preds, zero_division=0))\n\n# === Step 3: Find best threshold (by F1 score) ===\nbest_idx = np.argmax(f1s)\nbest_threshold = thresholds[best_idx]\nprint(f\"\\n✅ Best Threshold = {best_threshold:.2f} with F1 = {f1s[best_idx]:.4f}\")\n\n# === Step 4: Final predictions using best threshold ===\nfinal_preds = [1 if p >= best_threshold else 0 for p in y_scores]\n\n# === Step 5: Classification Report and Confusion Matrix ===\nprint(\"\\n📊 Classification Report:\")\nprint(classification_report(y_true, final_preds, target_names=[\"Normal\", \"Abnormal\"]))\n\nprint(\"\\n📉 Confusion Matrix:\")\ncm = confusion_matrix(y_true, final_preds)\nprint(cm)\n\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=[\"Normal\", \"Abnormal\"])\ndisp.plot(cmap=\"Blues\")\nplt.title(f\"Confusion Matrix (Threshold = {best_threshold:.2f})\")\nplt.grid(False)\nplt.show()\n\n# === Optional: Plot Precision, Recall, F1 vs Threshold ===\nplt.figure(figsize=(10, 5))\nplt.plot(thresholds, precisions, label=\"Precision\", color='blue')\nplt.plot(thresholds, recalls, label=\"Recall\", color='green')\nplt.plot(thresholds, f1s, label=\"F1 Score\", color='red')\nplt.axvline(best_threshold, color='gray', linestyle='--', label=f\"Best Threshold = {best_threshold:.2f}\")\nplt.xlabel(\"Threshold\")\nplt.ylabel(\"Score\")\nplt.title(\"Metrics vs Threshold\")\nplt.legend()\nplt.grid(True)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:40:36.306338Z","iopub.execute_input":"2025-07-10T09:40:36.306958Z","iopub.status.idle":"2025-07-10T09:40:37.098453Z","shell.execute_reply.started":"2025-07-10T09:40:36.306934Z","shell.execute_reply":"2025-07-10T09:40:37.097727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_image_class(image_path):\n    import torch\n    import torch.nn as nn\n    import numpy as np\n    from PIL import Image\n    from torchvision import transforms\n    from torchvision.models import resnet101, densenet121\n    import segmentation_models_pytorch as smp\n\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    # === Load transforms\n    transform_chexnet = transforms.Compose([\n        transforms.Resize((256, 256)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n    ])\n    transform_seg = transforms.Compose([\n        transforms.Resize((512, 512)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n    ])\n\n    # === Load segmentation model\n    def load_seg_model(path):\n        model = smp.Unet(encoder_name='resnet34', in_channels=3, classes=1)\n        model.load_state_dict(torch.load(path, map_location=DEVICE))\n        model.eval()\n        return model.to(DEVICE)\n\n    seg_model = load_seg_model(\"/kaggle/input/x-ray-segmention-model/xray_Segmention_model.pth\")\n\n    # === Load classification models\n    def get_resnet101_model():\n        model = resnet101(weights=None)\n        model.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(2048, 2))\n        return model.to(DEVICE)\n\n    def get_chexnet_model():\n        model = densenet121(weights=None)\n        model.classifier = nn.Linear(1024, 2)\n        return model.to(DEVICE)\n\n    model_seg = get_resnet101_model()\n    model_seg.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/best_fc_only_resnet101_epoch10_Segmentation.pth\", map_location=DEVICE))\n    model_seg.eval()\n\n    model_chex_all = get_chexnet_model()\n    model_chex_all.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch7.pth\", map_location=DEVICE))\n    model_chex_all.eval()\n\n    model_chex_2 = get_chexnet_model()\n    model_chex_2.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch9_2classes.pth\", map_location=DEVICE))\n    model_chex_2.eval()\n\n    # === Load and preprocess image\n    image = Image.open(image_path).convert(\"RGB\")\n\n    # → CheXNet input\n    img_chexnet = transform_chexnet(image).unsqueeze(0).to(DEVICE)\n\n    # → Segmentation preprocessing\n    img_seg_input = transform_seg(image).unsqueeze(0).to(DEVICE)\n    with torch.no_grad():\n        mask = torch.sigmoid(seg_model(img_seg_input)).squeeze().cpu().numpy()\n        mask = (mask > 0.5).astype(np.float32)\n\n    # Apply mask\n    image_resized = image.resize((512, 512))\n    image_np = np.array(image_resized).astype(np.float32) / 255.0\n    masked_image = image_np * np.expand_dims(mask, axis=-1)\n    masked_pil = Image.fromarray((masked_image * 255).astype(np.uint8))\n    img_segmented = transform_chexnet(masked_pil).unsqueeze(0).to(DEVICE)\n\n    # === Inference from each model\n    def get_prob(model, img_tensor):\n        with torch.no_grad():\n            out = model(img_tensor)\n            prob = torch.softmax(out, dim=1)[0, 1].item()\n        return prob\n\n    p1 = get_prob(model_seg, img_segmented)\n    p2 = get_prob(model_chex_all, img_chexnet)\n    p3 = get_prob(model_chex_2, img_chexnet)\n\n    # === Soft voting\n    probs = [p1, p2, p3]\n    final_prob = np.mean(probs)\n    pred_class = 1 if final_prob >= 0.71  else 0\n\n    label = \"Abnormal\" if pred_class == 1 else \"Normal\"\n\n    # === Print result\n    print(f\"\\n🖼️ Image: {image_path}\")\n    print(f\"📊 Ensemble Probability: {final_prob:.4f}\")\n    print(f\"🧠 Prediction: {label}\")\n    return label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:40:05.968702Z","iopub.execute_input":"2025-07-10T09:40:05.968942Z","iopub.status.idle":"2025-07-10T09:40:05.982422Z","shell.execute_reply.started":"2025-07-10T09:40:05.968925Z","shell.execute_reply":"2025-07-10T09:40:05.981842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"predict_image_class(\"/kaggle/input/our-pacs-data-yolo-640/Images/Images/01010078100.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:40:05.983145Z","iopub.execute_input":"2025-07-10T09:40:05.98339Z","iopub.status.idle":"2025-07-10T09:40:08.613704Z","shell.execute_reply.started":"2025-07-10T09:40:05.983366Z","shell.execute_reply":"2025-07-10T09:40:08.612938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_image_class(dicom_path):\n    import torch\n    import torch.nn as nn\n    import numpy as np\n    import pydicom\n    from PIL import Image\n    from torchvision import transforms\n    from torchvision.models import resnet101, densenet121\n    import segmentation_models_pytorch as smp\n\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    # === Load transforms\n    transform_chexnet = transforms.Compose([\n        transforms.Resize((256, 256)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n    ])\n    transform_seg = transforms.Compose([\n        transforms.Resize((512, 512)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.5482]*3, std=[0.2667]*3)\n    ])\n\n    # === Load segmentation model\n    def load_seg_model(path):\n        model = smp.Unet(encoder_name='resnet34', in_channels=3, classes=1)\n        model.load_state_dict(torch.load(path, map_location=DEVICE))\n        model.eval()\n        return model.to(DEVICE)\n\n    seg_model = load_seg_model(\"/kaggle/input/x-ray-segmention-model/xray_Segmention_model.pth\")\n\n    # === Load classification models\n    def get_resnet101_model():\n        model = resnet101(weights=None)\n        model.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(2048, 2))\n        return model.to(DEVICE)\n\n    def get_chexnet_model():\n        model = densenet121(weights=None)\n        model.classifier = nn.Linear(1024, 2)\n        return model.to(DEVICE)\n\n    model_seg = get_resnet101_model()\n    model_seg.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/best_fc_only_resnet101_epoch10_Segmentation.pth\", map_location=DEVICE))\n    model_seg.eval()\n\n    model_chex_all = get_chexnet_model()\n    model_chex_all.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch7.pth\", map_location=DEVICE))\n    model_chex_all.eval()\n\n    model_chex_2 = get_chexnet_model()\n    model_chex_2.load_state_dict(torch.load(\"/kaggle/input/models-normal-vs-abnormal/chexnet_fc_only_epoch9_2classes.pth\", map_location=DEVICE))\n    model_chex_2.eval()\n\n    # === Load DICOM and convert to PIL image\n    dicom = pydicom.dcmread(dicom_path)\n    image_array = dicom.pixel_array.astype(np.float32)\n\n    # Normalize to [0,255] then convert to uint8\n    image_array -= image_array.min()\n    image_array /= image_array.max()\n    image_array *= 255\n    image_array = image_array.astype(np.uint8)\n\n    # If grayscale, convert to RGB\n    if len(image_array.shape) == 2:\n        image_pil = Image.fromarray(image_array).convert(\"RGB\")\n    else:\n        image_pil = Image.fromarray(image_array)\n\n    # → CheXNet input\n    img_chexnet = transform_chexnet(image_pil).unsqueeze(0).to(DEVICE)\n\n    # → Segmentation preprocessing\n    img_seg_input = transform_seg(image_pil).unsqueeze(0).to(DEVICE)\n    with torch.no_grad():\n        mask = torch.sigmoid(seg_model(img_seg_input)).squeeze().cpu().numpy()\n        mask = (mask > 0.5).astype(np.float32)\n\n    # Apply mask\n    image_resized = image_pil.resize((512, 512))\n    image_np = np.array(image_resized).astype(np.float32) / 255.0\n    masked_image = image_np * np.expand_dims(mask, axis=-1)\n    masked_pil = Image.fromarray((masked_image * 255).astype(np.uint8))\n    img_segmented = transform_chexnet(masked_pil).unsqueeze(0).to(DEVICE)\n\n    # === Inference from each model\n    def get_prob(model, img_tensor):\n        with torch.no_grad():\n            out = model(img_tensor)\n            prob = torch.softmax(out, dim=1)[0, 1].item()\n        return prob\n\n    p1 = get_prob(model_seg, img_segmented)\n    p2 = get_prob(model_chex_all, img_chexnet)\n    p3 = get_prob(model_chex_2, img_chexnet)\n\n    # === Soft voting\n    probs = [p1, p2, p3]\n    final_prob = np.mean(probs)\n    pred_class = 1 if final_prob >= 0.71 else 0\n    label = \"Abnormal\" if pred_class == 1 else \"Normal\"\n\n    # === Print result\n    print(f\"\\n📁 DICOM File: {dicom_path}\")\n    print(f\"📊 Ensemble Probability: {final_prob:.4f}\")\n    print(f\"🧠 Prediction: {label}\")\n    return label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:40:08.61482Z","iopub.execute_input":"2025-07-10T09:40:08.61512Z","iopub.status.idle":"2025-07-10T09:40:08.628604Z","shell.execute_reply.started":"2025-07-10T09:40:08.615095Z","shell.execute_reply":"2025-07-10T09:40:08.627812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\npredict_image_class(\"/kaggle/input/vinbigdata-chest-xray-abnormalities-detection/train/000434271f63a053c4128a0ba6352c7f.dicom\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-10T09:40:08.630732Z","iopub.execute_input":"2025-07-10T09:40:08.631349Z","iopub.status.idle":"2025-07-10T09:40:11.670161Z","shell.execute_reply.started":"2025-07-10T09:40:08.63133Z","shell.execute_reply":"2025-07-10T09:40:11.66953Z"}},"outputs":[],"execution_count":null}]}