{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":13451,"datasetId":654585,"databundleVersionId":1188070}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"The Problem Statement\n\nContext: A patient arrives with Acute Neurological Deficit (AND). \n\nThe Danger: If they have a bleed (hemorrhage) and we give them blood thinners for a stroke, they will die. \n\nThe Task: We must build a \"Red Light\" filter. We will use the RSNA Intracranial Hemorrhage Dataset to train an AI that looks at a CT scan and answers one critical question: \n\n\"Is there blood?\" The Data: The dataset contains DICOM files (medical images) and a CSV file telling us if each image has a bleed and what type (Subdural, Epidural, etc.)","metadata":{}},{"cell_type":"code","source":"# Cell 1: Install and Import Libraries\n\n# In Kaggle, we often need to install MONAI first as it's not always default\n!pip install -q monai pydicom\n\nimport os\nimport pydicom\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport torch\n\n# Import MONAI specific transforms\nfrom monai.transforms import (\n    Compose,\n    LoadImage,\n    Resize,\n    ScaleIntensity,\n    ToTensor,\n)\n\nprint(\"Libraries installed and imported successfully!\")\nprint(f\"Torch Version: {torch.__version__}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:45:39.089592Z","iopub.execute_input":"2025-12-27T02:45:39.090238Z","iopub.status.idle":"2025-12-27T02:46:21.311527Z","shell.execute_reply.started":"2025-12-27T02:45:39.090207Z","shell.execute_reply":"2025-12-27T02:46:21.310846Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation: First, we need to install the tools.\n\nPydicom: The library that lets Python read medical .dcm files (which contain patient data + the image).\n\nMONAI (Medical Open Network for AI): The professional framework you need for your research paper. It handles 3D volumes and medical-specific transformations better than standard TensorFlow/PyTorch.","metadata":{}},{"cell_type":"code","source":"# Cell 2: Define Paths and Load the CSV \"Map\"\n\n# This is the standard path in the Kaggle RSNA Hemorrhage competition\nBASE_PATH = \"/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/\"\nTRAIN_DIR = os.path.join(BASE_PATH, \"stage_2_train/\")\nTEST_DIR = os.path.join(BASE_PATH, \"stage_2_test/\")\n\n# Load the CSV file\ntrain_df = pd.read_csv(os.path.join(BASE_PATH, \"stage_2_train.csv\"))\n\n# Let's peek at the data to understand it\nprint(\"Original Data Format:\")\nprint(train_df.head())\n\n# CLEANING: The 'ID' column looks like \"ID_000039fa0_epidural\".\n# We need to split this into \"Image ID\" and \"Hemorrhage Type\".\ntrain_df['ImageID'] = train_df['ID'].apply(lambda x: \"ID_\" + x.split('_')[1])\ntrain_df['Subtype'] = train_df['ID'].apply(lambda x: x.split('_')[2])\n\n# Now let's pivot the table so each Image ID has a row with 0/1 for each bleed type\n# This makes it easier to train the model later\npivot_df = train_df.pivot_table(index='ImageID', columns='Subtype', values='Label')\n\n# Reset index to make ImageID a column again\npivot_df.reset_index(inplace=True)\n\nprint(\"\\nCleaned Data Format (One row per patient image):\")\nprint(pivot_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:21.312796Z","iopub.execute_input":"2025-12-27T02:46:21.313457Z","iopub.status.idle":"2025-12-27T02:46:33.174334Z","shell.execute_reply.started":"2025-12-27T02:46:21.313397Z","shell.execute_reply":"2025-12-27T02:46:33.173608Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cell 2: Define Paths and Load the CSV \"Map\"\n\n# This is the standard path in the Kaggle RSNA Hemorrhage competition\nBASE_PATH = \"/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/\"\nTRAIN_DIR = os.path.join(BASE_PATH, \"stage_2_train/\")\nTEST_DIR = os.path.join(BASE_PATH, \"stage_2_test/\")\n\n# Load the CSV file\ntrain_df = pd.read_csv(os.path.join(BASE_PATH, \"stage_2_train.csv\"))\n\n# Let's peek at the data to understand it\nprint(\"Original Data Format:\")\nprint(train_df.head())\n\n# CLEANING: The 'ID' column looks like \"ID_000039fa0_epidural\".\n# We need to split this into \"Image ID\" and \"Hemorrhage Type\".\ntrain_df['ImageID'] = train_df['ID'].apply(lambda x: \"ID_\" + x.split('_')[1])\ntrain_df['Subtype'] = train_df['ID'].apply(lambda x: x.split('_')[2])\n\n# Now let's pivot the table so each Image ID has a row with 0/1 for each bleed type\n# This makes it easier to train the model later\npivot_df = train_df.pivot_table(index='ImageID', columns='Subtype', values='Label')\n\n# Reset index to make ImageID a column again\npivot_df.reset_index(inplace=True)\n\nprint(\"\\nCleaned Data Format (One row per patient image):\")\nprint(pivot_df.head())","metadata":{}},{"cell_type":"code","source":"# Cell 3: The Physics Engine (Windowing Function) - FIXED (No NaNs!)\n\ndef window_image(img, window_center, window_width):\n    img_min = window_center - window_width // 2\n    img_max = window_center + window_width // 2\n    img = np.clip(img, img_min, img_max)\n    return img\n\ndef get_windowed_image(dcm):\n    pixel_array = dcm.pixel_array.astype(np.float32)\n    intercept = dcm.RescaleIntercept\n    slope = dcm.RescaleSlope\n    pixel_array = pixel_array * slope + intercept\n\n    # 3 Windows\n    brain_img = window_image(pixel_array, 40, 80)\n    subdural_img = window_image(pixel_array, 80, 200)\n    soft_img = window_image(pixel_array, 40, 380)\n    \n    # --- FIX STARTS HERE ---\n    # We define a tiny number (epsilon) to prevent division by zero\n    eps = 1e-10\n    \n    # Safe Normalization Function\n    def normalize(x):\n        return (x - x.min()) / (x.max() - x.min() + eps)\n\n    brain_img = normalize(brain_img)\n    subdural_img = normalize(subdural_img)\n    soft_img = normalize(soft_img)\n    # --- FIX ENDS HERE ---\n\n    return np.dstack((brain_img, subdural_img, soft_img))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:33.175262Z","iopub.execute_input":"2025-12-27T02:46:33.175587Z","iopub.status.idle":"2025-12-27T02:46:33.182191Z","shell.execute_reply.started":"2025-12-27T02:46:33.17556Z","shell.execute_reply":"2025-12-27T02:46:33.181355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation:A normal JPG image has pixels from 0 (Black) to 255 (White).A CT Scan has Hounsfield Units (HU) ranging from -1000 (Air) to +3000 (Bone).\n\nThe Problem: If you just look at the raw numbers, the skull is so bright ($+1000$) that the brain ($+40$) and the blood ($+70$) both look like identical dark grey blobs. The AI will fail.\n\nThe Solution (Windowing): We force the computer to focus only on the range where brain matter and blood exist.\n\nBrain Window: Center=40, Width=80. (Good for general structure).\nSubdural Window: Center=80, Width=200. (Specifically highlights blood).","metadata":{}},{"cell_type":"code","source":"# Cell 4: Visualizing a \"Positive\" Case (Hemorrhage)\n\n# Let's find an image ID that actually has a hemorrhage (Label = 1)\n# 'any' column being 1 means there is SOME type of bleed\nhemorrhage_case = pivot_df[pivot_df['any'] == 1].iloc[0]['ImageID']\n\n# Construct the file path\nfile_path = os.path.join(TRAIN_DIR, hemorrhage_case + \".dcm\")\n\n# Load the DICOM file\ndcm = pydicom.dcmread(file_path)\n\n# Apply our windowing function\nprocessed_image = get_windowed_image(dcm)\n\n# Plotting\nfig, ax = plt.subplots(1, 4, figsize=(20, 5))\n\n# Show the combined 3-channel image (What the AI sees)\nax[0].imshow(processed_image)\nax[0].set_title(\"AI Input (3-Channel Stack)\")\n\n# Show individual channels (What the Human Radiologist looks for)\nax[1].imshow(processed_image[:, :, 0], cmap='gray')\nax[1].set_title(\"Channel 1: Brain Window\")\n\nax[2].imshow(processed_image[:, :, 1], cmap='gray')\nax[2].set_title(\"Channel 2: Blood Window (Look here!)\")\n\nax[3].imshow(processed_image[:, :, 2], cmap='gray')\nax[3].set_title(\"Channel 3: Soft Tissue/Bone\")\n\nplt.show()\n\nprint(f\"Viewing Case: {hemorrhage_case}\")\nprint(\"If you see a bright white spot in Channel 2, that is the hemorrhage.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:33.184043Z","iopub.execute_input":"2025-12-27T02:46:33.184343Z","iopub.status.idle":"2025-12-27T02:46:33.92624Z","shell.execute_reply.started":"2025-12-27T02:46:33.184318Z","shell.execute_reply":"2025-12-27T02:46:33.925434Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation: Now, let's actually grab a file from the dataset and look at it. We will display a patient who has a hemorrhage. You will see three versions of the same brain:\n\nBrain Window: Good for anatomy.\n\nBlood Window: The bleed will pop out as bright white.\n\nBone Window: Shows the skull clearly.","metadata":{}},{"cell_type":"code","source":"# Cell 5: The Custom Data Engine (PyTorch Dataset)\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import StratifiedKFold\n\nclass IntracranialDataset(Dataset):\n    def __init__(self, df, path, labels=True, transform=None):\n        self.path = path\n        self.data = df\n        self.transform = transform\n        self.labels = labels\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        # 1. Get the Image ID and construct the path\n        img_id = self.data.iloc[idx]['ImageID']\n        img_path = os.path.join(self.path, img_id + \".dcm\")\n        \n        # 2. Load and Window the image (Using our function from Cell 3)\n        try:\n            dcm = pydicom.dcmread(img_path)\n            img = get_windowed_image(dcm) # Returns (512, 512, 3)\n        except:\n            # Fallback for corrupted files (rare but happens)\n            img = np.zeros((512, 512, 3))\n        \n        # 3. Transpose dimensions for PyTorch: (Height, Width, Channels) -> (Channels, Height, Width)\n        # PyTorch expects [3, 512, 512]\n        img = img.transpose(2, 0, 1)\n        \n        # 4. Convert to Tensor (Float32)\n        img = torch.tensor(img, dtype=torch.float32)\n\n        # 5. Apply MONAI Augmentations (if any)\n        if self.transform:\n            img = self.transform(img)\n\n        # 6. Return Image + Label (if training)\n        if self.labels:\n            # Get the 6 labels: [epidural, intraparenchymal, intraventricular, subarachnoid, subdural, any]\n            label_cols = ['epidural', 'intraparenchymal', 'intraventricular', 'subarachnoid', 'subdural', 'any']\n            labels = torch.tensor(self.data.iloc[idx][label_cols].values.astype('float32'))\n            return img, labels\n        else:\n            return img\n\nprint(\"Dataset Class defined. Ready to load data.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:33.928304Z","iopub.execute_input":"2025-12-27T02:46:33.928658Z","iopub.status.idle":"2025-12-27T02:46:33.935852Z","shell.execute_reply.started":"2025-12-27T02:46:33.928633Z","shell.execute_reply":"2025-12-27T02:46:33.935133Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation: In Deep Learning, you don't load 20,000 images at once (it crashes the RAM). You create a Dataset Class. This is a script that waits for the AI to ask: \"Give me Batch #1\", and then it quickly:\n\nLocates 16 random images.\n\nLoads them.\n\nApplies the Windowing (Physics).\n\nHands them to the GPU.\n\nWe are using a monai.data.Dataset logic here but wrapping it in a custom PyTorch class to handle the specific \"Windowing\" function we wrote in Cell 3.","metadata":{}},{"cell_type":"code","source":"# Cell 6: Defining the Model (ResNet Backbone)\n\nimport torchvision.models as models\nimport torch.nn as nn\n\nclass IntracranialResNet(nn.Module):\n    def __init__(self, num_classes=6):\n        super(IntracranialResNet, self).__init__()\n        \n        # Load a pre-trained ResNet18\n        # 'weights=\"DEFAULT\"' downloads the ImageNet weights\n        self.backbone = models.resnet18(weights='DEFAULT')\n        \n        # ResNet18 input is usually 3 channels (RGB), which matches our Windowing stack perfectly.\n        \n        # Get the number of inputs to the final layer\n        in_features = self.backbone.fc.in_features\n        \n        # Replace the final \"Fully Connected\" (fc) layer\n        # Output: 6 logits (one for each bleed type)\n        self.backbone.fc = nn.Linear(in_features, num_classes)\n        \n    def forward(self, x):\n        return self.backbone(x)\n\n# Initialize the model to check if it works\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = IntracranialResNet()\nmodel.to(device)\n\nprint(f\"Model Architecture created on {device}\")\nprint(\"Input Layer: Expects 3 Channels (Brain, Blood, Bone windows)\")\nprint(\"Output Layer: 6 Classes (Bleed Types)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:33.936683Z","iopub.execute_input":"2025-12-27T02:46:33.936945Z","iopub.status.idle":"2025-12-27T02:46:34.661743Z","shell.execute_reply.started":"2025-12-27T02:46:33.936916Z","shell.execute_reply":"2025-12-27T02:46:34.660953Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation (Crucial for Paper): We are not building a CNN from scratch (that takes weeks to train). We are using Transfer Learning.\n\nBackbone: ResNet18 or EfficientNet-B0. These models have already \"seen\" millions of images (ImageNet). They know what \"edges\" and \"curves\" look like.\n\nThe Modification: We cut off the last layer (which classifies cats/dogs) and replace it with a Medical Head that has 6 outputs (the 6 types of hemorrhage).\n\nSigmoid Activation: Since a patient can have multiple types of bleed at once (e.g., Subdural AND Subarachnoid), we use Sigmoid (independent probabilities), not Softmax.","metadata":{}},{"cell_type":"code","source":"# Cell 7: Training Loop with SAFETY (Gradient Clipping)\n\n# Re-create the model to wipe out the 'NaN' weights from the previous failed run\nmodel = IntracranialResNet()\nmodel.to(device)\n\n# Weighted Loss Function\n# This forces the model to pay 5x more attention to positive cases\npos_weight = torch.tensor([5.0] * 6).to(device) # 6 classes\ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n# Lower learning rate slightly to be safe (1e-4 -> 5e-5)\noptimizer = torch.optim.Adam(model.parameters(), lr=5e-5)\n\ndef train_one_epoch(model, loader, optimizer, criterion):\n    model.train()\n    running_loss = 0.0\n    \n    for inputs, labels in loader:\n        inputs, labels = inputs.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        \n        loss.backward()\n        \n        # --- FIX: CLIP GRADIENTS ---\n        # This prevents the \"Exploding Gradient\" problem\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        \n        optimizer.step()\n        \n        running_loss += loss.item() * inputs.size(0)\n        \n    epoch_loss = running_loss / len(loader.dataset)\n    return epoch_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:34.662804Z","iopub.execute_input":"2025-12-27T02:46:34.66312Z","iopub.status.idle":"2025-12-27T02:46:34.862639Z","shell.execute_reply.started":"2025-12-27T02:46:34.663088Z","shell.execute_reply":"2025-12-27T02:46:34.861782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 7 (REVISED): Balanced Data Loader & Stronger Weights\n# RUN THIS to fix the low scores!\n\n# 1. Separate the dataset into \"Sick\" and \"Healthy\"\nsick_patients = pivot_df[pivot_df['any'] == 1]\nhealthy_patients = pivot_df[pivot_df['any'] == 0]\n\nprint(f\"Total Sick Patients available: {len(sick_patients)}\")\nprint(f\"Total Healthy Patients available: {len(healthy_patients)}\")\n\n# 2. Create a Balanced Subset for Training\n# We will take ALL the sick patients we found, and match them with an equal number of healthy ones.\nn_samples = min(len(sick_patients), 1000) # Take up to 1000 sick cases\n\nbalanced_sick = sick_patients.sample(n_samples, random_state=42)\nbalanced_healthy = healthy_patients.sample(n_samples, random_state=42)\n\n# Combine and shuffle\nbalanced_df = pd.concat([balanced_sick, balanced_healthy]).sample(frac=1).reset_index(drop=True)\n\nprint(f\"New Balanced Dataset Created: {len(balanced_df)} images (50% Sick / 50% Healthy)\")\n\n# 3. Split into Train/Val\ntrain_len = int(len(balanced_df) * 0.8)\nval_len = len(balanced_df) - train_len\ntrain_data, val_data = torch.utils.data.random_split(balanced_df, [train_len, val_len])\n\n# 4. Create Loaders\ntrain_dataset = IntracranialDataset(balanced_df.iloc[train_data.indices], TRAIN_DIR)\nval_dataset = IntracranialDataset(balanced_df.iloc[val_data.indices], TRAIN_DIR)\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=16, shuffle=False, num_workers=2)\n\n# 5. Define Model & Stronger Loss\nmodel = IntracranialResNet() # Reset model brain\nmodel.to(device)\n\n# WEIGHT BOOST: We increase the penalty.\n# Since data is now 50/50, we don't need a massive weight, but let's keep it at 2.0 to be safe.\npos_weight = torch.tensor([2.0] * 6).to(device)\ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4) # Standard learning rate\n\nprint(\"System Re-Armed. Ready to Re-Train.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:46:34.863714Z","iopub.execute_input":"2025-12-27T02:46:34.864011Z","iopub.status.idle":"2025-12-27T02:46:35.172113Z","shell.execute_reply.started":"2025-12-27T02:46:34.863989Z","shell.execute_reply":"2025-12-27T02:46:35.17139Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Explanation: This is where the learning happens. We define the Loss Function.\n\nLoss Function: BCEWithLogitsLoss. This is standard for multi-label classification. It measures how \"wrong\" the model's prediction is compared to the actual label.\n\nOptimizer: Adam. It adjusts the neuron weights to minimize the error.","metadata":{}},{"cell_type":"code","source":"# Cell 8: EXECUTE TRAINING\n# Run this cell to start the learning process.\n\n# Number of times the model sees the entire dataset\nnum_epochs = 3 \n\nprint(f\"Starting Training for {num_epochs} epochs...\")\nprint(\"-\" * 30)\n\nbest_val_loss = float('inf') # Track the best score\n\nfor epoch in range(num_epochs):\n    # --- TRAIN PHASE ---\n    train_loss = train_one_epoch(model, train_loader, optimizer, criterion)\n    \n    # --- VALIDATION PHASE ---\n    # Switch model to 'eval' mode (turns off learning features like Dropout)\n    model.eval()\n    val_loss = 0.0\n    \n    # No gradient needed for validation (saves memory)\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            \n            # Forward pass only\n            outputs = model(inputs)\n            \n            # Calculate error\n            loss = criterion(outputs, labels)\n            val_loss += loss.item() * inputs.size(0)\n            \n    # Calculate average loss\n    val_loss = val_loss / len(val_loader.dataset)\n    \n    # --- REPORTING ---\n    print(f\"Epoch [{epoch+1}/{num_epochs}]\")\n    print(f\"   Train Loss: {train_loss:.4f} (Lower is better)\")\n    print(f\"   Val Loss:   {val_loss:.4f} (Lower is better)\")\n    \n    # --- SAVING CHECKPOINT ---\n    # Only save if this is the best version so far\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(model.state_dict(), \"best_hemorrhage_model.pth\")\n        print(\"   >>> Model Improved & Saved!\")\n    \n    print(\"-\" * 30)\n\nprint(\"Training Complete! The best model is saved as 'best_hemorrhage_model.pth'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T02:57:15.825341Z","iopub.execute_input":"2025-12-27T02:57:15.825965Z","iopub.status.idle":"2025-12-27T02:58:39.759616Z","shell.execute_reply.started":"2025-12-27T02:57:15.825934Z","shell.execute_reply":"2025-12-27T02:58:39.758717Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"What this code does:\n\nEpochs: It runs through the entire dataset 3 times (you can increase this later).\n\nTraining Loop: It updates the model weights to get smarter.\n\nValidation Loop: It pauses to test itself on images it hasn't seen, to make sure it's not cheating (overfitting).\n\nSaving: It saves the brain of the model (model.pth) so you don't lose progress.","metadata":{}},{"cell_type":"code","source":"# Cell 9: INFERENCE (Testing the AI)\n\ndef predict_single_patient(model, dataset, index):\n    \"\"\"\n    Takes a patient index, runs the AI, and compares to the real doctor's label.\n    \"\"\"\n    model.eval() # Set to evaluation mode\n    \n    # 1. Get the data\n    img, label = dataset[index]\n    \n    # 2. Prepare for model (Add batch dimension: [1, 3, 512, 512])\n    input_tensor = img.unsqueeze(0).to(device)\n    \n    # 3. Ask AI for diagnosis\n    with torch.no_grad():\n        output = model(input_tensor)\n        # Apply Sigmoid to get probability (0% to 100%)\n        probabilities = torch.sigmoid(output).squeeze().cpu().numpy()\n    \n    # 4. Decode the result\n    bleed_types = ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural', 'ANY BLEED']\n    real_label = label.numpy()\n    \n    print(f\"--- Case Analysis for Patient Index {index} ---\")\n    \n    # Create a nice table\n    print(f\"{'Condition':<20} | {'AI Probability':<15} | {'Actual Diagnosis (Doctor)'}\")\n    print(\"-\" * 60)\n    \n    for i, bleed in enumerate(bleed_types):\n        prob = probabilities[i]\n        truth = real_label[i]\n        \n        # Format the output\n        prob_str = f\"{prob*100:.1f}%\"\n        truth_str = \"POSITIVE\" if truth == 1 else \"Negative\"\n        \n        # Highlight high risk in stars\n        marker = \"!!!\" if prob > 0.5 else \"\"\n        \n        print(f\"{bleed:<20} | {prob_str:<15} {marker} | {truth_str}\")\n\n# --- RUN THE TEST ---\n# Let's check patient #5 (Change this number to test others)\npredict_single_patient(model, val_dataset, index=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T03:00:37.691963Z","iopub.execute_input":"2025-12-27T03:00:37.692561Z","iopub.status.idle":"2025-12-27T03:00:37.717119Z","shell.execute_reply.started":"2025-12-27T03:00:37.692533Z","shell.execute_reply":"2025-12-27T03:00:37.716511Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"What is this? Training is useless if we can't see the result. This cell picks a random patient from the test set, feeds their scan into your new AI, and prints out the diagnosis.\n\nWhy this matters for your Project: This simulates the \"AI Pre-screen\" step in your workflow. This is exactly what the system would do when a patient hits the ER door.","metadata":{}},{"cell_type":"code","source":"# Cell 10: The \"Stress Test\" (Find a Positive Case)\n\ndef test_positive_case(model, dataset):\n    print(\"Searching for a patient with a HEMORRHAGE...\")\n    \n    # Loop through the dataset until we find a sick patient\n    for i in range(len(dataset)):\n        img, label = dataset[i]\n        \n        # label[5] is the 'any' column (1 = Bleed Present)\n        if label[5] == 1:\n            print(f\"Found Positive Case at Index {i}!\")\n            \n            # Run the prediction function we wrote earlier\n            predict_single_patient(model, dataset, index=i)\n            break\n\n# Run the test\ntest_positive_case(model, val_dataset)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T03:00:55.861025Z","iopub.execute_input":"2025-12-27T03:00:55.861321Z","iopub.status.idle":"2025-12-27T03:00:55.891891Z","shell.execute_reply.started":"2025-12-27T03:00:55.86129Z","shell.execute_reply":"2025-12-27T03:00:55.891256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"In medical datasets, 80-90% of patients are \"Negative.\" A lazy AI can cheat by just guessing \"Negative\" every single time and getting 90% accuracy.\n\nTo prove your AI is actually looking at the image and not just guessing, we need to see if it screams \"YES\" when there is actual blood.\n\nIt searches your validation set for a patient who actually has a bleed and tests the AI on them. We want to see those probabilities jump to 80-99%.","metadata":{}},{"cell_type":"code","source":"# Cell 11: Explainable AI (Grad-CAM + Bounding Box)\n# This cell visualizes WHERE the AI is looking.\n\nimport cv2\n\n# 1. Define the Grad-CAM Hook\nclass GradCAM:\n    def __init__(self, model, target_layer):\n        self.model = model\n        self.target_layer = target_layer\n        self.gradients = None\n        self.activations = None\n        \n        # Hook into the layer to save data during the pass\n        target_layer.register_forward_hook(self.save_activation)\n        target_layer.register_backward_hook(self.save_gradient)\n\n    def save_activation(self, module, input, output):\n        self.activations = output\n\n    def save_gradient(self, module, grad_input, grad_output):\n        self.gradients = grad_output[0]\n\n    def generate_heatmap(self, input_tensor, class_idx):\n        # Zero grads\n        self.model.zero_grad()\n        \n        # Forward pass\n        output = self.model(input_tensor)\n        \n        # Backward pass for the specific class we care about (e.g., Subdural)\n        target = output[0][class_idx]\n        target.backward()\n\n        # Generate Heatmap (GAP logic)\n        gradients = self.gradients.data.cpu().numpy()[0]\n        activations = self.activations.data.cpu().numpy()[0]\n        \n        weights = np.mean(gradients, axis=(1, 2))\n        cam = np.zeros(activations.shape[1:], dtype=np.float32)\n\n        for i, w in enumerate(weights):\n            cam += w * activations[i]\n\n        cam = np.maximum(cam, 0) # ReLU\n        cam = cv2.resize(cam, (512, 512)) # Resize to image size\n        cam = cam - np.min(cam)\n        cam = cam / np.max(cam) # Normalize 0-1\n        return cam\n\n# 2. Initialize CAM on the last layer of ResNet\n# \"layer4\" is the final convolutional block in ResNet18\ncam_engine = GradCAM(model, model.backbone.layer4)\n\ndef visualize_prediction(dataset, num_examples=3):\n    count = 0\n    # Search for positive cases in the validation set\n    for i in range(len(dataset)):\n        if count >= num_examples: break\n        \n        img_tensor, label = dataset[i]\n        \n        # Only look at Positive cases (ANY BLEED = 1)\n        if label[5] == 1: \n            count += 1\n            \n            # Prepare Input\n            input_tensor = img_tensor.unsqueeze(0).to(device)\n            \n            # 1. Get Prediction\n            output = model(input_tensor)\n            probs = torch.sigmoid(output).squeeze().cpu().detach().numpy()\n            \n            # Find the strongest bleed type (e.g., Subdural) to visualize\n            # We skip index 5 (Any) to find the specific subtype\n            subtype_idx = np.argmax(probs[:5]) \n            subtype_name = ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural'][subtype_idx]\n            \n            # 2. Generate Heatmap for that subtype\n            heatmap = cam_engine.generate_heatmap(input_tensor, subtype_idx)\n            \n            # 3. Create Visualization\n            # Get the \"Blood Window\" (Channel 1) for display\n            original_img = img_tensor.cpu().numpy().transpose(1, 2, 0)\n            blood_window = original_img[:, :, 1] # Green Channel = Blood Window\n            \n            # Convert to RGB for plotting\n            img_display = np.stack([blood_window]*3, axis=-1)\n            \n            # Overlay Heatmap (Blue-Red)\n            heatmap_color = cv2.applyColorMap(np.uint8(255 * heatmap), cv2.COLORMAP_JET)\n            heatmap_color = np.float32(heatmap_color) / 255\n            overlay = heatmap_color * 0.4 + img_display * 0.6\n            \n            # 4. Draw Approximate Bounding Box\n            # Threshold heatmap to find the \"hot\" spot\n            thresh = np.uint8(255 * (heatmap > 0.5))\n            contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n            \n            if contours:\n                # Find biggest contour\n                c = max(contours, key=cv2.contourArea)\n                x, y, w, h = cv2.boundingRect(c)\n                # Draw Rectangle on Overlay\n                cv2.rectangle(overlay, (x, y), (x+w, y+h), (0, 1, 0), 2) # Green Box\n            \n            # 5. Plot\n            fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n            \n            ax[0].imshow(blood_window, cmap='gray')\n            ax[0].set_title(f\"Original Input (Blood Window)\\nPt Index: {i}\")\n            ax[0].axis('off')\n            \n            ax[1].imshow(overlay)\n            ax[1].set_title(f\"AI Focus (Heatmap + Box)\\nPred: {subtype_name} ({probs[subtype_idx]*100:.1f}%)\")\n            ax[1].axis('off')\n            \n            plt.show()\n            print(f\"Case {count}: AI detected {subtype_name}. The Green Box is the approximate localization.\")\n\n# Run the visualizer\nvisualize_prediction(val_dataset, num_examples=3)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Since we trained a Classifier (which says \"YES/NO\") and not a Detector (which draws boxes), the model doesn't natively output a box. However, we can use a technique called Grad-CAM (Gradient-weighted Class Activation Mapping) to \"ask\" the AI:\n\n\"Which pixels in the image made you think this was a bleed?\"\n\nI have written a special cell (Cell 11) that does exactly this. It creates a Heatmap showing the AI's focus and draws a Red Box around the hottest area.\n\nCell 11: The \"X-Ray Vision\" (Grad-CAM & Bounding Box)\nWhat this code does:\n\nHooks into the Brain: It attaches a \"spy wire\" to the last layer of the ResNet (layer4).\n\nGenerates Heatmap: It highlights the pixels that \"excited\" the neurons the most.\n\nDraws the Box: We use OpenCV to find the center of that hotspot and draw a rectangle.\n\nLoop: It finds 3 different positive cases for you to study.","metadata":{}},{"cell_type":"code","source":"# Cell 12: Visualize Specific Patient (Index 8)\n# Run this to see WHY the AI suspected Subdural for Patient 8\n\ndef visualize_specific_patient(dataset, index):\n    img_tensor, label = dataset[index]\n    \n    # Check if there is actually a bleed\n    if label[5] == 1:\n        print(f\"Visualizing Patient {index} (POSITIVE CASE)...\")\n    else:\n        print(f\"Visualizing Patient {index} (Negative Case)...\")\n\n    # Prepare Input\n    input_tensor = img_tensor.unsqueeze(0).to(device)\n    \n    # 1. Get Prediction\n    output = model(input_tensor)\n    probs = torch.sigmoid(output).squeeze().cpu().detach().numpy()\n    \n    # We want to visualize the 'Subdural' class specifically (Index 4)\n    # ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural', 'ANY']\n    target_class_idx = 4 \n    class_name = \"Subdural\"\n    \n    print(f\"Generating Heatmap for: {class_name} (Prob: {probs[target_class_idx]*100:.1f}%)\")\n\n    # 2. Generate Heatmap\n    # Make sure 'cam_engine' from Cell 11 is defined!\n    heatmap = cam_engine.generate_heatmap(input_tensor, target_class_idx)\n    \n    # 3. Create Visualization\n    # Get the \"Blood Window\" (Channel 1)\n    original_img = img_tensor.cpu().numpy().transpose(1, 2, 0)\n    blood_window = original_img[:, :, 1] \n    \n    # Convert to RGB\n    img_display = np.stack([blood_window]*3, axis=-1)\n    \n    # Overlay Heatmap (Blue-Red)\n    heatmap_color = cv2.applyColorMap(np.uint8(255 * heatmap), cv2.COLORMAP_JET)\n    heatmap_color = np.float32(heatmap_color) / 255\n    overlay = heatmap_color * 0.4 + img_display * 0.6\n    \n    # 4. Draw Box\n    thresh = np.uint8(255 * (heatmap > 0.5))\n    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    \n    if contours:\n        c = max(contours, key=cv2.contourArea)\n        x, y, w, h = cv2.boundingRect(c)\n        cv2.rectangle(overlay, (x, y), (x+w, y+h), (0, 1, 0), 2)\n    \n    # 5. Plot\n    fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n    \n    ax[0].imshow(blood_window, cmap='gray')\n    ax[0].set_title(f\"Patient {index}: Blood Window\")\n    ax[0].axis('off')\n    \n    ax[1].imshow(overlay)\n    ax[1].set_title(f\"AI Focus: {class_name}\")\n    ax[1].axis('off')\n    \n    plt.show()\n\n# EXECUTE\nvisualize_specific_patient(val_dataset, index=8)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The Diagnosis of Your Model\nThe Good News (It's Learning):\n\nLook at the specific types. The AI gave Subdural the highest score (11.7%) compared to Epidural (2.3%) or others.\n\nCrucial: It correctly identified the subtype even though the confidence is low. It \"knows\" this doesn't look like an Epidural bleed.\n\nThe \"Bad\" News (It's Hesitant):\n\nThe total probability is only 18.7%. In a hospital, we need this to be > 50% (and ideally > 90%).\n\nWhy? Your model is suffering from \"Class Imbalance Anxiety.\"\n\nIn medical datasets, 90% of images are Healthy (Negative).\n\nThe model has learned that if it guesses \"Negative\" it is right 90% of the time. So, it is afraid to bet high on \"Positive.\"\n\nImmediate Next Step: The \"Eye Test\" (Visualization)\nBefore we fix the math to raise the score, let's see what the AI saw in Patient Index 8 that made it suspect \"Subdural.\"\n\nUse the code below to generate the Heatmap for Patient 8.\n\nWhat to look for: A Subdural Hematoma usually looks like a crescent moon hugging the skull.\n\nThe Green Box: See if the box lands on that white crescent edge.","metadata":{}},{"cell_type":"markdown","source":"\nLITERATURE STUDY ON 6 OUTPUTS\n\n1. Epidural Hematoma (EDH)The Concept: A bleed between the Skull and the Dura Mater (the tough outer covering of the brain).Cause: usually a skull fracture tearing a high-pressure artery (Middle Meningeal Artery).Speed: Expands fast. Very dangerous.DICOM Representation (The \"Lemon\"):Shape: It looks like a Lens or a Lemon (Biconvex).Why? The dura is stuck tightly to the skull sutures (cracks), so the blood pushes inward, creating a bulge. It cannot cross the suture lines.Visual: A bright white, distinct oval shape pushing into the brain.\n\n\n2. Subdural Hematoma (SDH)The Concept: A bleed between the Dura Mater and the Arachnoid (the middle layer).Cause: Tearing of bridging veins (low pressure). Common in elderly falls or rapid acceleration/deceleration.DICOM Representation (The \"Banana\"):Shape: It looks like a Crescent Moon or a Banana (Concave).Why? It is not stuck to sutures, so it spreads out along the curve of the skull.Visual: A long, thin white strip hugging the skull.AI Challenge: This is the hardest for AI because it can be very subtle and thin (looks like a slightly thicker skull bone). This is exactly what your Patient Index 8 had.3\n\n3. Subarachnoid Hemorrhage (SAH)The Concept: Bleeding into the Subarachnoid Space (where the Cerebrospinal Fluid flows).Cause: Ruptured Aneurysm (\"Thunderclap Headache\").DICOM Representation (The \"Star\" or \"Cracks\"):Shape: It fills the Sulci (the wrinkles) and Cisterns (the central tanks).Visual: Look for bright white lines tracing the \"cracks\" of the brain. Often looks like a white Star or Spider in the center of the brain.Texture: It looks \"stringy\" rather than a solid blob.\n\n\n4. Intraparenchymal Hemorrhage (IPH)The Concept: Bleeding inside the brain tissue itself.Cause: High Blood Pressure (Hypertension) or Stroke hemorrhagic transformation.DICOM Representation (The \"Blob\"):Shape: An irregular, solid white cloud or blob sitting right in the grey matter.Associated Feature: Often surrounded by a dark ring. This is Edema (swelling/water) which is dark grey.Location: Anywhere, but often deep in the brain (Basal Ganglia).\n\n\n5. Intraventricular Hemorrhage (IVH)The Concept: Bleeding into the Ventricles (the hollow, fluid-filled cavities in the center of the brain).Cause: Usually secondary (an IPH leaks into the ventricles).DICOM Representation (The \"White Butterfly\"):Shape: The ventricles usually look like a Black Butterfly (filled with water/CSF).Visual: If the fluid turns Bright White, that is blood.Gravity: Blood is heavy. If the patient is lying on their back, you might see a \"fluid level\" where white blood settles at the bottom of the black ventricle.\n\n6. The \"ANY\" Label (The Safety Switch)The Concept: This is not a specific disease. It is a logical OR gate.The Math: If (Epidural=1 OR Subdural=1 OR Subarachnoid=1 ...) THEN Any=1.Project Relevance: This is your Exclusion Gate.In your SSSIHMS workflow, if ANY == 1 (Probability > 50%), the Stroke Protocol (IVT) is aborted immediately.If ANY == 0, the patient is cleared for the next AI model (Ischemia Detection).\n","metadata":{}},{"cell_type":"code","source":"# Cell 13: The Pathology Comparison Gallery\n# Run this to generate a side-by-side comparison of all bleed types.\n\ndef show_pathology_gallery(dataset):\n    # Define the types we want to compare\n    pathologies = ['Epidural', 'Subdural', 'Subarachnoid', 'Intraparenchymal', 'Intraventricular']\n    \n    # Create a figure for the gallery\n    fig, axes = plt.subplots(1, 5, figsize=(25, 5))\n    fig.suptitle('Comparison of Hemorrhage Types (Blood Window)', fontsize=20, weight='bold')\n    \n    found_counts = {p: 0 for p in pathologies}\n    \n    # Loop through dataset to find one example of each\n    # (We limit the search to first 1000 images to save time)\n    for i in range(len(dataset)):\n        # Stop if we found everything\n        if all(c > 0 for c in found_counts.values()):\n            break\n            \n        img_tensor, label = dataset[i]\n        label_np = label.numpy() # [Epi, IntraP, IntraV, SAH, Sub, Any]\n        \n        # Map label index to name\n        # 0: Epidural, 1: Intraparenchymal, 2: Intraventricular, 3: Subarachnoid, 4: Subdural\n        \n        current_pathology = None\n        \n        # Check specific slots (Order matters to match the list above)\n        if label_np[0] == 1 and found_counts['Epidural'] == 0:\n            current_pathology = 'Epidural'\n            ax_idx = 0\n        elif label_np[4] == 1 and found_counts['Subdural'] == 0:\n            current_pathology = 'Subdural'\n            ax_idx = 1\n        elif label_np[3] == 1 and found_counts['Subarachnoid'] == 0:\n            current_pathology = 'Subarachnoid'\n            ax_idx = 2\n        elif label_np[1] == 1 and found_counts['Intraparenchymal'] == 0:\n            current_pathology = 'Intraparenchymal'\n            ax_idx = 3\n        elif label_np[2] == 1 and found_counts['Intraventricular'] == 0:\n            current_pathology = 'Intraventricular'\n            ax_idx = 4\n            \n        # If we found a new one, plot it!\n        if current_pathology:\n            found_counts[current_pathology] += 1\n            \n            # Get Blood Window (Channel 1)\n            original_img = img_tensor.cpu().numpy().transpose(1, 2, 0)\n            blood_window = original_img[:, :, 1]\n            \n            axes[ax_idx].imshow(blood_window, cmap='gray')\n            axes[ax_idx].set_title(f\"{current_pathology}\\n(Patient Index: {i})\", fontsize=14)\n            axes[ax_idx].axis('off')\n            \n            # Add a colored border to make it look professional\n            for spine in axes[ax_idx].spines.values():\n                spine.set_edgecolor('red')\n                spine.set_linewidth(2)\n\n    plt.tight_layout()\n    plt.show()\n\n# Generate the Gallery\nshow_pathology_gallery(val_dataset)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The \"Pathology Gallery\" Code\nThis script will:\n\nSearch your dataset for the first example of an Epidural, then a Subdural, then a Subarachnoid, etc.\n\nExtract the \"Blood Window\" (which makes bleeds white).\n\nPlot them in a nice 2x3 Grid so you can see the \"Lemon\" vs. the \"Banana\" vs. the \"Star\" instantly.\n","metadata":{}},{"cell_type":"code","source":"# Cell 14: Automated Bleed Quantification (Volume & Location)\n# This calculates \"How Much\" (mL) and shows \"Where\" (Mask).\n\ndef analyze_bleed_severity(dataset, index):\n    # 1. Get the Image and Prediction first\n    img_tensor, label = dataset[index]\n    input_tensor = img_tensor.unsqueeze(0).to(device)\n    \n    # Run AI\n    model.eval()\n    with torch.no_grad():\n        output = model(input_tensor)\n        probs = torch.sigmoid(output).squeeze().cpu().numpy()\n    \n    # Check if AI thinks there is a bleed (Threshold > 50%)\n    if probs[5] < 0.5: # Index 5 is 'ANY'\n        print(f\"Patient {index}: AI suggests NO BLEED (Safe). No volume to calculate.\")\n        return\n\n    # 2. Extract the \"Blood Window\" (Channel 1)\n    # This channel was windowed specifically to make blood white (HU 80-200)\n    original_img = img_tensor.cpu().numpy().transpose(1, 2, 0)\n    blood_window = original_img[:, :, 1] # 0.0 to 1.0 float\n    \n    # 3. Image Processing to find \"Where\"\n    # Blood is bright in this window. We threshold top 20% brightness.\n    # (Adjust this threshold based on calibration, 0.6 is a good start for windowed data)\n    bleed_mask = blood_window > 0.6 \n    \n    # Clean up noise (remove tiny specs)\n    kernel = np.ones((3,3), np.uint8)\n    bleed_mask = cv2.morphologyEx(bleed_mask.astype(np.uint8), cv2.MORPH_OPEN, kernel)\n    \n    # 4. Calculate \"How Much\" (Volume)\n    # Assumptions for CT:\n    # Pixel spacing ~ 0.5mm x 0.5mm\n    # Slice thickness ~ 5mm\n    voxel_volume_mm3 = 0.5 * 0.5 * 5.0 \n    \n    pixel_count = np.sum(bleed_mask)\n    volume_mm3 = pixel_count * voxel_volume_mm3\n    volume_mL = volume_mm3 / 1000.0 # Convert to mL\n    \n    # 5. Determine Severity\n    if volume_mL < 10: severity = \"MILD\"\n    elif volume_mL < 30: severity = \"MODERATE\"\n    else: severity = \"SEVERE (Life Threatening)\"\n    \n    # 6. Report\n    subtype_idx = np.argmax(probs[:5])\n    subtype = ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural'][subtype_idx]\n    \n    print(f\"--- HEMORRHAGE ANALYSIS REPORT (Patient {index}) ---\")\n    print(f\"1. Detection:   POSITIVE ({probs[5]*100:.1f}% Confidence)\")\n    print(f\"2. Type:        {subtype}\")\n    print(f\"3. Est. Volume: {volume_mL:.2f} mL\")\n    print(f\"4. Severity:    {severity}\")\n    print(\"-\" * 40)\n    \n    # 7. Visualize \"Where\"\n    fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n    \n    ax[0].imshow(blood_window, cmap='gray')\n    ax[0].set_title(\"Input CT (Blood Window)\")\n    ax[0].axis('off')\n    \n    # Create red overlay for the bleed\n    overlay = np.zeros_like(blood_window)\n    overlay[bleed_mask == 1] = 1\n    \n    ax[1].imshow(blood_window, cmap='gray')\n    ax[1].imshow(overlay, cmap='Reds', alpha=0.5) # Overlay red on top\n    ax[1].set_title(f\"Automated Localization\\n(Red Area = ~{volume_mL:.1f} mL)\")\n    ax[1].axis('off')\n    \n    plt.show()\n\n# TEST IT on a positive patient (e.g., Index 8 or 5)\n# Note: You need a positive case for this to work well.\nanalyze_bleed_severity(val_dataset, index=113)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T03:38:20.583787Z","iopub.execute_input":"2025-12-27T03:38:20.584079Z","iopub.status.idle":"2025-12-27T03:38:20.900242Z","shell.execute_reply.started":"2025-12-27T03:38:20.584053Z","shell.execute_reply":"2025-12-27T03:38:20.899664Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Note: A pure \"Classifier\" (ResNet) only says YES/NO. To get volume (mL), usually we use a Segmentation model (U-Net). However, since we are prototyping, I have written a Heuristic Algorithm for this cell.\n\nLogic: It takes the \"Blood Window\" (where blood is white), applies a strict brightness threshold, and counts the pixels.\n\nMath: Volume = Pixel_Count * Pixel_Area * Slice_Thickness. (We assume standard 5mm thickness for this prototype).","metadata":{}},{"cell_type":"code","source":"# Cell 15: Interactive Patient Viewer (Phase 1 Final Tool)\n# Use the slider to scroll through patients.\n\nimport ipywidgets as widgets\nfrom IPython.display import display\n\ndef view_patient_interactive(index):\n    # 1. Get Data\n    dataset = val_dataset # Use validation set\n    if index >= len(dataset):\n        print(\"Index out of range.\")\n        return\n        \n    img_tensor, label = dataset[index]\n    \n    # 2. Run AI Prediction\n    input_tensor = img_tensor.unsqueeze(0).to(device)\n    model.eval()\n    with torch.no_grad():\n        output = model(input_tensor)\n        probs = torch.sigmoid(output).squeeze().cpu().numpy()\n    \n    # 3. Decode Outputs (The 6 Neurons)\n    class_names = ['Epidural', 'Intraparenchymal', 'Intraventricular', 'Subarachnoid', 'Subdural', 'ANY BLEED']\n    \n    # Find the main culprit\n    subtype_idx = np.argmax(probs[:5]) # Ignore 'Any' for subtype\n    main_subtype = class_names[subtype_idx]\n    main_prob = probs[subtype_idx]\n    any_prob = probs[5]\n    \n    # 4. Prepare Images\n    # Extract \"Blood Window\" (Channel 1)\n    original_img = img_tensor.cpu().numpy().transpose(1, 2, 0)\n    blood_window = original_img[:, :, 1]\n    \n    # Generate Red Mask (Thresholding for Visualization)\n    # Why is it Red? Because pixel intensity > 0.6 (High Hounsfield Unit)\n    mask = blood_window > 0.6 \n    \n    # Clean noise\n    mask = cv2.morphologyEx(mask.astype(np.uint8), cv2.MORPH_OPEN, np.ones((2,2), np.uint8))\n    \n    # Create Overlay\n    overlay = np.stack([blood_window]*3, axis=-1) # Grayscale base\n    # Add Red tint where mask is True\n    overlay[mask==1] = [1.0, 0.0, 0.0] # [R, G, B] -> Red\n    \n    # 5. DISPLAY (The \"Cockpit\")\n    fig, ax = plt.subplots(1, 2, figsize=(16, 6))\n    \n    # Left: Raw Input\n    ax[0].imshow(blood_window, cmap='gray')\n    ax[0].set_title(f\"Patient {index}: Raw CT (Blood Window)\", fontsize=14)\n    ax[0].axis('off')\n    \n    # Right: AI Analysis\n    ax[1].imshow(overlay)\n    ax[1].set_title(f\"AI Detection (Red = Hemorrhage)\", fontsize=14)\n    ax[1].axis('off')\n    \n    plt.show()\n    \n    # 6. TEXT EXPLANATION\n    print(\"-\" * 60)\n    print(f\"--- AI DIAGNOSTIC REPORT (Patient {index}) ---\")\n    \n    # Logic: Why Red?\n    if any_prob > 0.5:\n        print(f\" STATUS: CRITICAL (Bleed Detected)\")\n        print(f\" WHY RED? The red pixels represent high-density tissue (>60 HU).\")\n        print(f\"   In this specific window, only BONE and BLOOD appear bright.\")\n        print(f\"   Since these red pixels are NOT the skull, the AI identifies them as BLOOD.\")\n    else:\n        print(f\" STATUS: NORMAL (No Active Bleed)\")\n        print(f\"   The image may look grey, but no pixels passed the 'Hemorrhage Threshold'.\")\n    \n    print(\"-\" * 60)\n    print(f\" PROBABILITY BREAKDOWN (The 6 Outputs):\")\n    \n    # Print all 6 outputs with a visual bar\n    for i, name in enumerate(class_names):\n        p = probs[i]\n        bar = \"█\" * int(p * 20) # Visual bar\n        print(f\"{name:<20} | {p*100:5.1f}% | {bar}\")\n        \n    print(\"-\" * 60)\n    print(f\" PRIMARY DIAGNOSIS: {main_subtype} ({main_prob*100:.1f}%)\")\n\n# Create the Slider\ninteract_slider = widgets.IntSlider(min=0, max=len(val_dataset)-1, step=1, value=0, description='Patient ID:')\nwidgets.interact(view_patient_interactive, index=interact_slider);","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T03:49:01.100802Z","iopub.execute_input":"2025-12-27T03:49:01.101696Z","iopub.status.idle":"2025-12-27T03:49:01.385967Z","shell.execute_reply.started":"2025-12-27T03:49:01.101663Z","shell.execute_reply":"2025-12-27T03:49:01.385303Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"What this code does:\n\nSlider: Adds a toggle bar to scroll through Patient Index (0 to 50).\n\nDual View: Shows Raw CT (Left) vs. AI Overlay (Right).\n\nDynamic Report: Automatically updates the text to explain why it flagged the case and which of the 6 outputs triggered the alarm.","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}