{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":90095,"databundleVersionId":10627580,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:31:45.576105Z","iopub.execute_input":"2025-02-21T08:31:45.576541Z","iopub.status.idle":"2025-02-21T08:31:45.584793Z","shell.execute_reply.started":"2025-02-21T08:31:45.576501Z","shell.execute_reply":"2025-02-21T08:31:45.583476Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport os\nimport numpy as np\nfrom PIL import Image\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# Function to compute bounding box from polygon points\ndef getc_bbox(points, image_width, image_height):\n    xs = [p[0] for p in points]\n    ys = [p[1] for p in points]\n    min_x = max(0, min(xs))\n    max_x = min(image_width, max(xs))\n    min_y = max(0, min(ys))\n    max_y = min(image_height, max(ys))\n    if min_x >= max_x or min_y >= max_y:\n        return None  # Invalid ROI\n    return [int(min_x), int(min_y), int(max_x), int(max_y)]\n# Paths (update these)\ntrain_dir = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/\"\n\n# Load JSON files\njson_files = [os.path.join(train_dir, f) for f in os.listdir(train_dir) if f.endswith('.json')]\n\n# 1. Class Distribution\nlabels = []\nfor json_file in json_files:\n    with open(json_file, 'r') as f:\n        data = json.load(f)\n    for shape in data['shapes']:\n        label = 1 if shape['label'] == 'Mitosis' else 0\n        labels.append(label)\ntotal_rois = len(labels)\nmitosis_count = sum(labels)\nnon_mitosis_count = total_rois - mitosis_count\nprint(f\"Total ROIs: {total_rois}\")\nprint(f\"Mitosis: {mitosis_count} ({mitosis_count/total_rois*100:.2f}%)\")\nprint(f\"Non-mitosis: {non_mitosis_count} ({non_mitosis_count/total_rois*100:.2f}%)\")\n\n# 2. ROI Size Distribution\nareas = []\nfor json_file in json_files:\n    with open(json_file, 'r') as f:\n        data = json.load(f)\n    image_width, image_height = Image.open(os.path.join(train_dir, json_file.replace('.json', '.jpg'))).size\n    \n    for shape in data['shapes']:\n        bbox = getc_bbox(shape['points'],image_width, image_height)\n        if not bbox:\n            continue\n        area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])\n        areas.append(area)\nareas = np.array(areas)\nprint(f\"ROI Area Stats - Mean: {areas.mean():.2f}, Median: {np.median(areas):.2f}, Min: {areas.min():.2f}, Max: {areas.max():.2f}, Std: {areas.std():.2f}\")\n\n# 3. ROIs per Image\nrois_per_image = [len(json.load(open(jf))['shapes']) for jf in json_files]\nrois_per_image = np.array(rois_per_image)\nprint(f\"ROIs per Image - Mean: {rois_per_image.mean():.2f}, Median: {np.median(rois_per_image):.2f}, Min: {rois_per_image.min()}, Max: {rois_per_image.max()}, Std: {rois_per_image.std():.2f}\")\n\n# 4. Spatial Distribution\ncentroids = []\nfor json_file in json_files:\n    with open(json_file, 'r') as f:\n        data = json.load(f)\n    for shape in data['shapes']:\n        points = np.array(shape['points'])\n        centroid = points.mean(axis=0)  # [x, y]\n        centroids.append(centroid)\ncentroids = np.array(centroids)\ngrid, _, _ = np.histogram2d(centroids[:, 0], centroids[:, 1], bins=8, range=[[0, 512], [0, 512]])\nprint(\"Spatial Distribution (8x8 grid counts):\")\nprint(grid.astype(int))\n\n# 5. Image Intensity Stats (sample 50 images)\nimage_files = [os.path.join(train_dir, f) for f in os.listdir(train_dir) if f.endswith('.jpg')][:50]\nmeans, stds = [], []\nfor img_file in image_files:\n    img = np.array(Image.open(img_file).convert('RGB')) / 255.0\n    means.append(img.mean(axis=(0, 1)))\n    stds.append(img.std(axis=(0, 1)))\nmeans = np.mean(means, axis=0)\nstds = np.mean(stds, axis=0)\nprint(f\"Image Intensity - R: mean={means[0]:.3f}, std={stds[0]:.3f}; G: mean={means[1]:.3f}, std={stds[1]:.3f}; B: mean={means[2]:.3f}, std={stds[2]:.3f}\")\n\n# 6. Mitosis vs. Non-mitosis Feature Comparison\nmitosis_intensities, non_mitosis_intensities = [], []\nfor json_file in json_files:\n    with open(json_file, 'r') as f:\n        data = json.load(f)\n    image_width, image_height = Image.open(os.path.join(train_dir, json_file.replace('.json', '.jpg'))).size\n    img = np.array(Image.open(os.path.join(train_dir, json_file.replace('.json', '.jpg'))).convert('RGB')) / 255.0\n    for shape in data['shapes']:\n        bbox = getc_bbox(shape['points'],image_width, image_height)\n        if not bbox:\n            continue\n        roi = img[bbox[1]:bbox[3], bbox[0]:bbox[2]]\n        intensity = roi.mean()\n        if shape['label'] == 'Mitosis':\n            mitosis_intensities.append(intensity)\n        else:\n            non_mitosis_intensities.append(intensity)\nprint(f\"Mitosis ROI Intensity - Mean: {np.mean(mitosis_intensities):.3f}, Std: {np.std(mitosis_intensities):.3f}\")\nprint(f\"Non-mitosis ROI Intensity - Mean: {np.mean(non_mitosis_intensities):.3f}, Std: {np.std(non_mitosis_intensities):.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:31:49.202247Z","iopub.execute_input":"2025-02-21T08:31:49.202654Z","iopub.status.idle":"2025-02-21T08:32:11.696696Z","shell.execute_reply.started":"2025-02-21T08:31:49.202621Z","shell.execute_reply":"2025-02-21T08:32:11.695747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport os\nimport math\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score\nimport pandas as pd\nfrom torchvision.models import vit_b_16, ViT_B_16_Weights\nimport torch.nn as nn\nimport torch.optim as optim\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.697913Z","iopub.execute_input":"2025-02-21T08:32:11.698239Z","iopub.status.idle":"2025-02-21T08:32:11.704247Z","shell.execute_reply.started":"2025-02-21T08:32:11.698213Z","shell.execute_reply":"2025-02-21T08:32:11.703119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.705933Z","iopub.execute_input":"2025-02-21T08:32:11.706249Z","iopub.status.idle":"2025-02-21T08:32:11.722147Z","shell.execute_reply.started":"2025-02-21T08:32:11.706222Z","shell.execute_reply":"2025-02-21T08:32:11.721153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.723740Z","iopub.execute_input":"2025-02-21T08:32:11.724011Z","iopub.status.idle":"2025-02-21T08:32:11.738910Z","shell.execute_reply.started":"2025-02-21T08:32:11.723987Z","shell.execute_reply":"2025-02-21T08:32:11.737741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_bbox(points, img_width, img_height):\n    \"\"\"Compute bounding box from polygon points, ensuring it stays within image bounds.\"\"\"\n    xs = [p[0] for p in points]  # Extract x-coordinates\n    ys = [p[1] for p in points]  # Extract y-coordinates\n    min_x = max(0, int(math.floor(min(xs))))  # Clamp to 0 as minimum\n    max_x = min(img_width, int(math.ceil(max(xs))))  # Clamp to image width as maximum\n    min_y = max(0, int(math.floor(min(ys))))  # Clamp to 0 as minimum\n    max_y = min(img_height, int(math.ceil(max(ys))))  # Clamp to image height as maximum\n    \n    # Check if the bounding box is valid\n    if min_x >= max_x or min_y >= max_y:\n        return None  # Return None for invalid bounding boxes\n    return [min_x, min_y, max_x, max_y]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.740086Z","iopub.execute_input":"2025-02-21T08:32:11.740356Z","iopub.status.idle":"2025-02-21T08:32:11.758375Z","shell.execute_reply.started":"2025-02-21T08:32:11.740333Z","shell.execute_reply":"2025-02-21T08:32:11.757227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def parse_training_json(json_path, train_dir):\n    \"\"\"Extract training samples from JSON annotations, handling varying image sizes.\"\"\"\n    with open(json_path, 'r') as f:\n        data = json.load(f)\n    image_path = os.path.join(train_dir, json_file.replace('.json', '.jpg'))\n    img = Image.open(image_path)  # Open image to get its size\n    img_width, img_height = img.size  # Get actual dimensions\n    samples = []\n    for shape in data['shapes']:\n        label = 1 if shape['label'] == 'Mitosis' else 0  # Binary label\n        bbox = get_bbox(shape['points'], img_width, img_height)\n        if bbox is not None:  # Only include valid bounding boxes\n            samples.append((image_path, bbox, label))\n    return samples","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.759816Z","iopub.execute_input":"2025-02-21T08:32:11.760217Z","iopub.status.idle":"2025-02-21T08:32:11.781656Z","shell.execute_reply.started":"2025-02-21T08:32:11.760178Z","shell.execute_reply":"2025-02-21T08:32:11.780418Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Parse test JSON files with dynamic image sizes\ndef parse_test_json(json_path, test_dir):\n    \"\"\"Extract test samples from JSON annotations, handling varying image sizes.\"\"\"\n    with open(json_path, 'r') as f:\n        data = json.load(f)\n    image_path = os.path.join(train_dir, json_file.replace('.json', '.jpg'))\n    img = Image.open(image_path)  # Open image to get its size\n    img_width, img_height = img.size  # Get actual dimensions\n    samples = []\n    for shape in data['shapes']:\n        label_id = shape['label']  # e.g., \"Blank1\"\n        bbox = get_bbox(shape['points'], img_width, img_height)\n        if bbox is not None:  # Only include valid bounding boxes\n            samples.append((image_path, bbox, label_id))\n    return samples","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.782800Z","iopub.execute_input":"2025-02-21T08:32:11.783116Z","iopub.status.idle":"2025-02-21T08:32:11.797570Z","shell.execute_reply.started":"2025-02-21T08:32:11.783091Z","shell.execute_reply":"2025-02-21T08:32:11.796498Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# Custom Dataset for training/validation with empty ROI handling\nclass MitosisDataset(Dataset):\n    \"\"\"Dataset class for training and validation data.\"\"\"\n    def __init__(self, samples, transform=None):\n        self.samples = samples\n        self.transform = transform\n        self.image_cache = {}  # Cache images to avoid reloading\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        image_path, bbox, label = self.samples[idx]\n        # Load or retrieve image from cache\n        if image_path not in self.image_cache:\n            image = Image.open(image_path).convert('RGB')\n            self.image_cache[image_path] = image\n        else:\n            image = self.image_cache[image_path]\n        \n        # Crop and handle empty ROIs\n        cropped = image.crop(bbox)\n        if cropped.size == (0, 0):  # Skip empty crops\n            return None\n        resized = cropped.resize((224, 224))  # Resize for model input\n        if self.transform:\n            resized = self.transform(resized)\n        return resized, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.801211Z","iopub.execute_input":"2025-02-21T08:32:11.801543Z","iopub.status.idle":"2025-02-21T08:32:11.819427Z","shell.execute_reply.started":"2025-02-21T08:32:11.801507Z","shell.execute_reply":"2025-02-21T08:32:11.818457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Custom Dataset for test data with empty ROI handling\nclass TestDataset(Dataset):\n    \"\"\"Dataset class for test data.\"\"\"\n    def __init__(self, samples, transform=None):\n        self.samples = samples\n        self.transform = transform\n        self.image_cache = {}  # Cache images to avoid reloading\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        image_path, bbox, label_id = self.samples[idx]\n        # Load or retrieve image from cache\n        if image_path not in self.image_cache:\n            image = Image.open(image_path).convert('RGB')\n            self.image_cache[image_path] = image\n        else:\n            image = self.image_cache[image_path]\n        \n        # Crop and handle empty ROIs\n        cropped = image.crop(bbox)\n        if cropped.size == (0, 0):  # Skip empty crops\n            return None\n        resized = cropped.resize((224, 224))  # Resize for model input\n        if self.transform:\n            resized = self.transform(resized)\n        image_id = os.path.basename(image_path).split('.')[0]\n        return resized, image_id, label_id\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.820720Z","iopub.execute_input":"2025-02-21T08:32:11.821030Z","iopub.status.idle":"2025-02-21T08:32:11.836056Z","shell.execute_reply.started":"2025-02-21T08:32:11.820988Z","shell.execute_reply":"2025-02-21T08:32:11.835035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define transformations for training and validation\ntrain_transform = transforms.Compose([\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.620, 0.405, 0.633], std=[0.162, 0.150, 0.123]),\n])\n\nval_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.620, 0.405, 0.633], std=[0.162, 0.150, 0.123]),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.837128Z","iopub.execute_input":"2025-02-21T08:32:11.837504Z","iopub.status.idle":"2025-02-21T08:32:11.856977Z","shell.execute_reply.started":"2025-02-21T08:32:11.837468Z","shell.execute_reply":"2025-02-21T08:32:11.856057Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/\"\ntest_dir = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_test\"\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.857862Z","iopub.execute_input":"2025-02-21T08:32:11.858167Z","iopub.status.idle":"2025-02-21T08:32:11.871724Z","shell.execute_reply.started":"2025-02-21T08:32:11.858135Z","shell.execute_reply":"2025-02-21T08:32:11.870688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"training_json_files = [os.path.join(train_dir, f) for f in os.listdir(train_dir) if f.endswith('.json')]\nall_training_samples = []\nfor json_file in training_json_files:\n    all_training_samples.extend(parse_training_json(json_file, train_dir))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:11.872817Z","iopub.execute_input":"2025-02-21T08:32:11.873218Z","iopub.status.idle":"2025-02-21T08:32:14.033328Z","shell.execute_reply.started":"2025-02-21T08:32:11.873180Z","shell.execute_reply":"2025-02-21T08:32:14.032431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_json_files = [os.path.join(test_dir, f) for f in os.listdir(test_dir) if f.endswith('.json')]\nall_test_samples = []\nfor json_file in test_json_files:\n    all_test_samples.extend(parse_test_json(json_file, test_dir))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.034188Z","iopub.execute_input":"2025-02-21T08:32:14.034436Z","iopub.status.idle":"2025-02-21T08:32:14.878265Z","shell.execute_reply.started":"2025-02-21T08:32:14.034414Z","shell.execute_reply":"2025-02-21T08:32:14.877301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_samples, val_samples = train_test_split(all_training_samples, test_size=0.2, random_state=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.879333Z","iopub.execute_input":"2025-02-21T08:32:14.879772Z","iopub.status.idle":"2025-02-21T08:32:14.886526Z","shell.execute_reply.started":"2025-02-21T08:32:14.879732Z","shell.execute_reply":"2025-02-21T08:32:14.885425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = MitosisDataset(train_samples, transform=train_transform)\nval_dataset = MitosisDataset(val_samples, transform=val_transform)\ntest_dataset = TestDataset(all_test_samples, transform=val_transform)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.887916Z","iopub.execute_input":"2025-02-21T08:32:14.888463Z","iopub.status.idle":"2025-02-21T08:32:14.942829Z","shell.execute_reply.started":"2025-02-21T08:32:14.888419Z","shell.execute_reply":"2025-02-21T08:32:14.941327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Custom collate function to filter out None values (empty ROIs)\ndef collate_fn(batch):\n    batch = [item for item in batch if item is not None]  # Remove None items\n    if len(batch) == 0:\n        return None  # Return None if batch is empty\n    return torch.utils.data.dataloader.default_collate(batch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.943344Z","iopub.status.idle":"2025-02-21T08:32:14.943717Z","shell.execute_reply":"2025-02-21T08:32:14.943539Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create DataLoaders\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4, collate_fn=collate_fn)\nval_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4, collate_fn=collate_fn)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=4, collate_fn=collate_fn)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.944695Z","iopub.status.idle":"2025-02-21T08:32:14.945038Z","shell.execute_reply":"2025-02-21T08:32:14.944895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load Vision Transformer model and modify the head\nmodel = vit_b_16(weights=ViT_B_16_Weights.DEFAULT)\nin_features = model.heads.head.in_features\nmodel.heads.head = nn.Linear(in_features, 1)  # Binary classification\nmodel.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:14.945950Z","iopub.status.idle":"2025-02-21T08:32:14.946259Z","shell.execute_reply":"2025-02-21T08:32:14.946137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define loss, optimizer, and scheduler\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:16.982964Z","iopub.execute_input":"2025-02-21T08:32:16.983332Z","iopub.status.idle":"2025-02-21T08:32:16.992217Z","shell.execute_reply.started":"2025-02-21T08:32:16.983306Z","shell.execute_reply":"2025-02-21T08:32:16.991201Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training function with empty batch handling\ndef train_model(model, train_loader, val_loader, criterion, optimizer, scheduler, num_epochs):\n    best_f1 = 0\n    for epoch in range(num_epochs):\n        # Training phase\n        model.train()\n        running_loss = 0.0\n        running_corrects = 0\n        total_train = 0\n        train_bar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs} [Training]\", leave=False)\n        for batch in train_bar:\n            if batch is None:  # Skip empty batches\n                continue\n            images, labels = batch\n            images, labels = images.to(device), labels.float().to(device)\n            optimizer.zero_grad()\n            outputs = model(images).squeeze()\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            # Update running loss and accuracy\n            running_loss += loss.item() * images.size(0)\n            preds = (torch.sigmoid(outputs) > 0.5).float()\n            running_corrects += torch.sum(preds == labels).item()\n            total_train += labels.size(0)\n            running_loss += loss.item()\n\n        # Update live training metrics\n            current_loss = running_loss / total_train\n            current_acc = running_corrects / total_train\n            train_bar.set_postfix({'Loss': f'{current_loss:.4f}', 'Acc': f'{current_acc:.4f}'})\n        train_loss = running_loss / total_train\n        train_acc = running_corrects / total_train\n        # Validation phase\n        model.eval()\n        val_loss = 0.0\n        running_val_loss = 0.0\n        running_val_corrects = 0\n        total_val = 0\n        preds_list, true_labels = [], []\n        val_bar = tqdm(val_loader, desc=f\"Epoch {epoch+1}/{num_epochs} [Validation]\", leave=False)\n        with torch.no_grad():\n            for batch in val_bar:\n                if batch is None:  # Skip empty batches\n                    continue\n                images, labels = batch\n                images, labels = images.to(device), labels.float().to(device)\n                outputs = model(images).squeeze()\n                loss = criterion(outputs, labels)\n                running_val_loss += loss.item() * images.size(0)\n                preds = (torch.sigmoid(outputs) > 0.5).float()\n                running_val_corrects += torch.sum(preds == labels).item()\n                total_val += labels.size(0)\n                \n                preds_list.extend(preds.cpu().numpy().tolist())\n                true_labels.extend(labels.cpu().numpy().tolist())\n                \n                current_val_loss = running_val_loss / total_val\n                current_val_acc = running_val_corrects / total_val\n                val_bar.set_postfix({'Loss': f'{current_val_loss:.4f}', 'Acc': f'{current_val_acc:.4f}'})\n        \n        val_loss = running_val_loss / total_val\n        val_acc = running_val_corrects / total_val\n        f1 = f1_score(true_labels, preds_list)\n        \n        print(f'Epoch {epoch+1}/{num_epochs} - '\n              f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | '\n              f'Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}, F1: {f1:.4f}')\n        \n        # Adjust learning rate based on F1 score and save best model\n        scheduler.step(f1)\n        if f1 > best_f1:\n            best_f1 = f1\n            torch.save(model.state_dict(), '/kaggle/working/best_model.pth')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:32:17.544320Z","iopub.execute_input":"2025-02-21T08:32:17.544716Z","iopub.status.idle":"2025-02-21T08:32:17.558286Z","shell.execute_reply.started":"2025-02-21T08:32:17.544686Z","shell.execute_reply":"2025-02-21T08:32:17.556701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train the model\ntrain_model(model, train_loader, val_loader, criterion, optimizer, scheduler, num_epochs=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T07:01:35.292220Z","iopub.execute_input":"2025-02-21T07:01:35.292557Z","iopub.status.idle":"2025-02-21T07:11:28.245891Z","shell.execute_reply.started":"2025-02-21T07:01:35.292530Z","shell.execute_reply":"2025-02-21T07:11:28.243774Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load('/kaggle/working/best_model.pth'))\nmodel.eval()\n\n# Predict on test set with empty ROI handling\npredictions_dict = {}\nwith torch.no_grad():\n    for batch in tqdm(test_loader, desc=\"Predicting\", leave=False):\n        if batch is None:  # Skip empty batches\n            continue\n        images, image_ids, label_ids = batch\n        images = images.to(device)\n        outputs = model(images).squeeze()\n        probs = torch.sigmoid(outputs).cpu().numpy()\n        preds = (probs > 0.5).astype(int)\n        for i in range(len(image_ids)):\n            predictions_dict[(image_ids[i], label_ids[i])] = preds[i]\n\n# Prepare submission file\nprint(predictions_dict)\nsubmission = pd.read_csv('/kaggle/input/glioma-mcd-2025/archive/Submission_template.csv')\nsubmission['Prediction'] = submission.apply(\n    lambda row: predictions_dict.get((row['Image ID'], row['Label ID']), 0), axis=1\n)\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\nprint(\"Submission file 'submission.csv' created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:42:27.050598Z","iopub.execute_input":"2025-02-21T08:42:27.051401Z","iopub.status.idle":"2025-02-21T08:42:27.096634Z","shell.execute_reply.started":"2025-02-21T08:42:27.051368Z","shell.execute_reply":"2025-02-21T08:42:27.095041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}