{"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":20604,"databundleVersionId":1357052}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==========================================\n# CHRONOSCAN: 3D TUMOR TRACKER (MVP)\n# Team: [Your Team Name]\n# ==========================================\n\n# INSTRUCTIONS:\n# Paste this entire code into a single cell in your Kaggle Notebook to verify setup.\n# Ensure 'LIDC-IDRI' dataset is added to the notebook inputs.\n\nimport os\nimport pydicom\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport scipy.ndimage\nimport random\n\n# --- CONFIGURATION ---\n# Path to the dataset in Kaggle (Verify this path in your sidebar!)\nDATASET_ROOT = \"/kaggle/input/lidc-idri\"\nTARGET_PATIENT = \"LIDC-IDRI-0001\" # We pick patient 0001 for the demo\n\n# ==========================================\n# MODULE 1: DATA LOADER (Member 2's Job)\n# ==========================================\ndef load_patient_volume(patient_id):\n    \"\"\"\n    Crawls the folder, finds .dcm files, sorts them, and stacks them into a 3D block.\n    \"\"\"\n    path = os.path.join(DATASET_ROOT, patient_id)\n    print(f\"Searching for data in: {path}...\")\n    \n    slices = []\n    # Recursive search for .dcm files\n    for root, dirs, files in os.walk(path):\n        for file in files:\n            if file.endswith(\".dcm\"):\n                slices.append(pydicom.dcmread(os.path.join(root, file)))\n    \n    if not slices:\n        print(\"ERROR: No slices found! Check dataset path.\")\n        return None\n        \n    # Sort slices by Z-position (ImagePositionPatient[2])\n    # This ensures the head is at the top and feet at the bottom\n    slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    \n    # Stack into a 3D numpy array\n    try:\n        volume_3d = np.stack([s.pixel_array for s in slices])\n        print(f\"✅ Success! Loaded {len(slices)} slices. Volume Shape: {volume_3d.shape}\")\n        return volume_3d\n    except Exception as e:\n        print(f\"Error stacking slices: {e}\")\n        return None\n\n# ==========================================\n# MODULE 2: TUMOR SIMULATOR (Member 2's Job)\n# ==========================================\ndef simulate_growth(volume, growth_factor=1.2):\n    \"\"\"\n    Takes the real scan (Time A) and artificially grows the tumor to create Time B.\n    For the MVP, we zoom into a specific region known to have a nodule.\n    \"\"\"\n    print(f\"Simulating Tumor Growth (Factor: {growth_factor}x)...\")\n    \n    # Create a copy for 'Time B'\n    vol_b = volume.copy()\n    \n    # HARDCODED TUMOR LOCATION FOR PATIENT 0001 (Found manually)\n    # These coordinates might need tuning if you pick a different patient\n    z, y, x = 65, 360, 360 # Approx location of a nodule\n    box = 30 # Box size\n    \n    # Extract the tumor box\n    tumor_region = volume[z-5:z+5, y-box:y+box, x-box:x+box]\n    \n    # Zoom it (Make it bigger)\n    # We use order=1 (linear interpolation) for speed\n    zoomed = scipy.ndimage.zoom(tumor_region, zoom=growth_factor, order=1)\n    \n    # Crop it back to fit the original hole\n    # (Simplified logic for MVP - just taking center crop)\n    z_mid, y_mid, x_mid = zoomed.shape[0]//2, zoomed.shape[1]//2, zoomed.shape[2]//2\n    \n    orig_z, orig_y, orig_x = tumor_region.shape\n    \n    crop = zoomed[\n        z_mid - orig_z//2 : z_mid + (orig_z - orig_z//2),\n        y_mid - orig_y//2 : y_mid + (orig_y - orig_y//2),\n        x_mid - orig_x//2 : x_mid + (orig_x - orig_x//2)\n    ]\n    \n    # Paste it back into Vol B\n    vol_b[z-5:z+5, y-box:y+box, x-box:x+box] = crop\n    \n    return vol_b\n\n# ==========================================\n# MODULE 3: VISUALIZER (Member 3's Job)\n# ==========================================\ndef show_comparison(vol_a, vol_b, slice_idx):\n    \"\"\"\n    Displays Scan A and Scan B side-by-side.\n    \"\"\"\n    plt.figure(figsize=(12, 6))\n    \n    plt.subplot(1, 2, 1)\n    plt.imshow(vol_a[slice_idx], cmap='gray')\n    plt.title(\"Scan A (Baseline)\")\n    plt.axis('off')\n    \n    plt.subplot(1, 2, 2)\n    plt.imshow(vol_b[slice_idx], cmap='gray')\n    plt.title(\"Scan B (Simulated Progression)\")\n    plt.axis('off')\n    \n    plt.show()\n\n# ==========================================\n# MAIN EXECUTION FLOW (Member 1's Job)\n# ==========================================\nif __name__ == \"__main__\":\n    # 1. Load Data\n    scan_a = load_patient_volume(TARGET_PATIENT)\n    \n    if scan_a is not None:\n        # 2. Simulate Growth\n        scan_b = simulate_growth(scan_a, growth_factor=1.3)\n        \n        # 3. Visualize Result\n        # We look at Slice 65 where we know the tumor is\n        show_comparison(scan_a, scan_b, slice_idx=65)\n        \n        print(\"\\nReady for AI Analysis Phase.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"🕵️‍♂️ DIAGNOSTIC MODE: ANALYZING INPUT STRUCTURE...\")\n\nbase_path = \"/kaggle/input\"\n\n# 1. List all Datasets attached\nif os.path.exists(base_path):\n    datasets = os.listdir(base_path)\n    print(f\"\\n📂 Datasets found in Input: {datasets}\")\nelse:\n    print(\"❌ '/kaggle/input' does not exist. Are you running this on Kaggle?\")\n\n# 2. Deep Dive: Look inside the first dataset\nfor dataset in datasets:\n    full_path = os.path.join(base_path, dataset)\n    print(f\"\\n🔎 Scanning dataset: '{dataset}'\")\n    \n    # List top-level folders inside this dataset\n    try:\n        top_level = os.listdir(full_path)\n        print(f\"   └── Top-level contents: {top_level}\")\n        \n        # Check extensions of the first 5 files found anywhere\n        count = 0\n        for root, dirs, files in os.walk(full_path):\n            if count > 5: break\n            for f in files:\n                print(f\"       📄 Found file: {f}\")\n                count += 1\n                if count > 5: break\n                \n    except Exception as e:\n        print(f\"   ❌ Error reading directory: {e}\")\n\nprint(\"\\n------------------------------------------------\")\nprint(\"💡 SOLUTION CHECK:\")\nprint(\"If you see files ending in '.xml' but NO '.dcm', you have the wrong dataset.\")\nprint(\"You need: 'LIDC-IDRI Images with DICOM Headers'\")\nprint(\"------------------------------------------------\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport matplotlib.pyplot as plt\nimport cv2 # Computer Vision library for standard images\n\nprint(\"🕵️‍♂️ PROBING NEW DATASET...\")\n\n# Standard path for added datasets\nstart_path = \"/kaggle/input\"\n\nfor root, dirs, files in os.walk(start_path):\n    # Look for standard images (png, jpg, npy)\n    images = [f for f in files if f.endswith(('.png', '.jpg', '.jpeg', '.npy'))]\n    \n    if len(images) > 0:\n        print(f\"✅ Found {len(images)} images in: {root}\")\n        \n        # Try to show the first one\n        first_img_path = os.path.join(root, images[0])\n        \n        try:\n            # If it's a numpy file\n            if first_img_path.endswith('.npy'):\n                img_data = np.load(first_img_path)\n                plt.imshow(img_data, cmap='gray')\n            else:\n                # If it's a normal image\n                img_data = cv2.imread(first_img_path)\n                plt.imshow(img_data)\n                \n            plt.title(f\"Sample: {images[0]}\")\n            plt.show()\n            print(\"found image finally\")\n            break\n        except Exception as e:\n            print(f\"Could not load image: {e}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pydicom\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# ==========================================\n# 1. CONFIGURATION\n# ==========================================\n# If you added \"OSIC Pulmonary Fibrosis Progression\", the path is usually:\nDATASET_ROOT = \"/kaggle/input/osic-pulmonary-fibrosis-progression/train\"\n\n# ==========================================\n# 2. THE 3D VOLUME LOADER\n# ==========================================\ndef load_full_3d_scan(patient_id):\n    path = os.path.join(DATASET_ROOT, patient_id)\n    print(f\" Loading 3D Volume for Patient: {patient_id}...\")\n    \n    slices = []\n    # 1. Find all .dcm files in the patient's folder\n    for file in os.listdir(path):\n        if file.endswith(\".dcm\"):\n            slices.append(pydicom.dcmread(os.path.join(path, file)))\n            \n    # 2. CRITICAL: Sort by Z-Position (Depth)\n    # This turns a pile of photos into a 3D object\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except AttributeError:\n        # Fallback: Sort by Instance Number if coordinates are missing\n        slices.sort(key=lambda x: int(x.InstanceNumber))\n        \n    # 3. Stack into 3D Array\n    volume_3d = np.stack([s.pixel_array for s in slices])\n    \n    print(f\"Success! 3D Shape: {volume_3d.shape}\")\n    print(f\"   (Depth: {volume_3d.shape[0]} slices, Height: {volume_3d.shape[1]}, Width: {volume_3d.shape[2]})\")\n    \n    return volume_3d\n\n# ==========================================\n# 3. EXECUTION & VISUALIZATION\n# ==========================================\n\n# 1. Auto-select the first patient in the folder\nall_patients = os.listdir(DATASET_ROOT)\ntarget_patient = all_patients[0] # Pick the first one\n\n# 2. Load the 3D Data\nvol_3d = load_full_3d_scan(target_patient)\n\n# 3. Prove it is 3D (Show 3 different depths)\nplt.figure(figsize=(15, 5))\n\n# Top of lungs\nplt.subplot(1, 3, 1)\ndepth_a = int(len(vol_3d) * 0.2)\nplt.imshow(vol_3d[depth_a], cmap='gray')\nplt.title(f\"Slice {depth_a} (Top)\")\n\n# Middle of lungs\nplt.subplot(1, 3, 2)\ndepth_b = int(len(vol_3d) * 0.5)\nplt.imshow(vol_3d[depth_b], cmap='gray')\nplt.title(f\"Slice {depth_b} (Middle)\")\n\n# Bottom of lungs\nplt.subplot(1, 3, 3)\ndepth_c = int(len(vol_3d) * 0.8)\nplt.imshow(vol_3d[depth_c], cmap='gray')\nplt.title(f\"Slice {depth_c} (Bottom)\")\n\nplt.show()\n\nprint(\"\\nSTATUS: We now have a REAL 3D Volume in memory.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport random\n\n# ==========================================\n# V2.0: ORGANIC TUMOR INJECTOR\n# ==========================================\ndef inject_organic_tumor(volume, center_coords, radius_mm):\n    \"\"\"\n    Injects a rough, bright, solid tumor into the 3D volume.\n    \"\"\"\n    vol_copy = volume.copy()\n    z_c, y_c, x_c = center_coords\n    \n    # 1. Determine \"White\" Color\n    # We look at the brightest bone in the scan and aim slightly lower (Tissue density)\n    max_val = np.max(volume)\n    min_val = np.min(volume)\n    \n    # In CT scans, bone is max. Tumor is usually 60-70% of max brightness.\n    # If the image is -1000 to 2000, we want around 100.\n    # If the image is 0 to 255, we want around 180.\n    tumor_density_core = max_val * 0.7 \n    \n    print(f\" Injecting Tumor with Brightness: {tumor_density_core:.2f} (Max is {max_val})\")\n\n    # 2. Define the Box\n    box = int(radius_mm + 4)\n    \n    for z in range(max(0, z_c-box), min(volume.shape[0], z_c+box)):\n        for y in range(max(0, y_c-box), min(volume.shape[1], y_c+box)):\n            for x in range(max(0, x_c-box), min(volume.shape[2], x_c+box)):\n                \n                # 3. Calculate Distance from center\n                dist = np.sqrt((z - z_c)**2 + (y - y_c)**2 + (x - x_c)**2)\n                \n                # 4. ORGANIC SHAPE LOGIC\n                # Instead of a hard threshold (dist < radius), we add noise\n                # \"Spiculation\": Random bumps on the surface\n                noise = random.uniform(-1.5, 1.5) \n                \n                if dist < (radius_mm + noise):\n                    # 5. TEXTURE LOGIC (Not just one flat color)\n                    # Tumors aren't perfectly uniform. They have texture.\n                    texture_noise = random.uniform(-50, 50)\n                    pixel_value = tumor_density_core + texture_noise\n                    \n                    # Inject!\n                    vol_copy[z, y, x] = pixel_value\n                    \n    return vol_copy\n\n# ==========================================\n# RE-RUN GENERATION\n# ==========================================\nprint(\" Injecting Realistic Baseline (Time A)...\")\nscan_a_real = inject_organic_tumor(vol_3d, tumor_coords, radius_mm=6)\n\nprint(\" Simulating Realistic Progression (Time B)...\")\nscan_b_real = inject_organic_tumor(vol_3d, tumor_coords, radius_mm=9)\n\n# ==========================================\n# VISUALIZE V2 (With Red Circles)\n# ==========================================\ntarget_z = tumor_coords[0]\ny_center, x_center = tumor_coords[1], tumor_coords[2]\n\nplt.figure(figsize=(12, 6))\n\n# Plot Scan A\nplt.subplot(1, 2, 1)\nplt.imshow(scan_a_real[target_z], cmap='gray')\nplt.title(\"Scan A: Baseline (Size: 6mm)\")\n# Add Red Circle (Radius slightly larger than tumor to enclose it)\ncircle1 = plt.Circle((x_center, y_center), 20, color='r', fill=False, linewidth=2)\nplt.gca().add_patch(circle1)\n\n# Plot Scan B\nplt.subplot(1, 2, 2)\nplt.imshow(scan_b_real[target_z], cmap='gray')\nplt.title(\"Scan B: Progression (Size: 9mm)\")\n# Add Red Circle (Radius slightly larger to match growth)\ncircle2 = plt.Circle((x_center, y_center), 25, color='r', fill=False, linewidth=2)\nplt.gca().add_patch(circle2)\n\nplt.show()\n\nprint(\" VISUALIZATION COMPLETE: Red circles indicate the target analysis zone.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -U transformers accelerate bitsandbytes huggingface_hub","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# FINAL FIX: INSTALL & LOAD TOGETHER\n# ==========================================\n\n# 1. INSTALL THE MISSING LIBRARIES (WAIT for this to finish)\nprint(\"📦 Installing bitsandbytes and friends...\")\n!pip install -U transformers accelerate bitsandbytes huggingface_hub\n\n# 2. IMPORT LIBRARIES\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\nfrom huggingface_hub import login\n\n# 3. LOGIN (PASTE YOUR TOKEN BELOW)\n# Get token from: https://huggingface.co/settings/tokens\nHF_TOKEN = \"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\" \n\ntry:\n    login(token=HF_TOKEN)\n    print(\"✅ Login Success.\")\nexcept:\n    print(\"⚠️ Login Failed. Did you paste your token inside the quotes?\")\n\n# 4. LOAD MODEL\nMODEL_ID = \"google/gemma-2b-it\"\nprint(f\"🧠 Loading {MODEL_ID}...\")\n\nbnb_config = BitsAndBytesConfig(\n    load_in_4bit=True,\n    bnb_4bit_quant_type=\"nf4\",\n    bnb_4bit_compute_dtype=torch.float16,\n)\n\ntry:\n    tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN)\n    model = AutoModelForCausalLM.from_pretrained(\n        MODEL_ID,\n        quantization_config=bnb_config,\n        device_map=\"auto\",\n        token=HF_TOKEN\n    )\n    print(\"\\n🎉 SUCCESS! MedGemma (Gemma-2B) is Live!\")\nexcept Exception as e:\n    print(f\"\\n❌ Error: {e}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# PHASE 5 (FINAL & FIXED): GENERATING THE REPORT\n# ==========================================\n\nimport numpy as np\nimport torch\n\ndef get_tumor_volume(radius_mm):\n    # Calculate volume of a sphere: 4/3 * pi * r^3\n    return (4/3) * np.pi * (radius_mm**3)\n\n# 1. GET THE MATH\nvol_a = get_tumor_volume(6)\nvol_b = get_tumor_volume(9)\ngrowth_pct = ((vol_b - vol_a) / vol_a) * 100\n\nprint(f\"📊 DATA PIPELINE:\")\nprint(f\"   - Baseline Volume: {vol_a:.2f} mm3\")\nprint(f\"   - Current Volume:  {vol_b:.2f} mm3\")\nprint(f\"   - Growth Rate:     {growth_pct:.1f}%\")\n\n# 2. CONSTRUCT THE PROMPT\nprompt = f\"\"\"<start_of_turn>user\nYou are an expert Thoracic Radiologist using RECIST 1.1 criteria.\nGenerate a concise clinical report for the following longitudinal 3D volumetric analysis.\n\nPatient Data:\n- ID: OSIC-001 (Pulmonary Fibrosis)\n- Scan Interval: 6 Months\n\nFindings (ChronoScan 3D Analysis):\n- Baseline Nodule Volume (Time A): {vol_a:.2f} mm3\n- Current Nodule Volume (Time B): {vol_b:.2f} mm3\n- Net Volumetric Change: {growth_pct:+.1f}%\n- Morphology: Solid, spiculated margins.\n\nTask:\n1. State the volumetric findings.\n2. Classify the progression status (Stable vs Progressive). (Threshold is +20% volume).\n3. Recommend next steps (Biopsy vs Follow-up).\n\nReport:<end_of_turn>\n<start_of_turn>model\n\"\"\"\n\n# 3. RUN INFERENCE (The Brain Thinks)\nprint(\"\\n🧠 MedGemma is generating the diagnosis...\")\n\n# --- THE FIX IS HERE ---\n# We verify where the model is living (cuda:0 or cuda:1)\ntarget_device = model.device \nprint(f\"   (Model is running on: {target_device})\")\n\n# We send the inputs to THAT specific device\ninputs = tokenizer(prompt, return_tensors=\"pt\").to(target_device)\n\noutputs = model.generate(\n    **inputs,           # Unpack dictionary\n    max_new_tokens=250, \n    temperature=0.7,    \n    do_sample=True\n)\n\n# 4. DECODE & DISPLAY\nresponse = tokenizer.decode(outputs[0], skip_special_tokens=True)\nreport_body = response.split(\"model\")[-1].strip()\n\nprint(\"\\n\" + \"=\"*50)\nprint(\"     🏥 CHRONOSCAN OFFICIAL REPORT\")\nprint(\"=\"*50)\nprint(report_body)\nprint(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 🏆 CHRONOSCAN: MASTER DEMO CELL\n# ==========================================\n# Run this to generate the Full Video Demo Output\n\nimport os\nimport pydicom\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport scipy.ndimage\nimport random\nimport torch\n\n# --- PART 1: RELOAD THE 3D VOLUME ---\n# We check if 'vol_3d' exists in memory. If not, we load it.\nif 'vol_3d' not in globals():\n    print(\"📦 Reloading Patient Data from OSIC Dataset...\")\n    DATA_PATH = \"/kaggle/input/osic-pulmonary-fibrosis-progression/train\"\n    try:\n        patients = os.listdir(DATA_PATH)\n        target_patient = patients[0] # Pick first patient\n        patient_dir = os.path.join(DATA_PATH, target_patient)\n        \n        slices = [pydicom.dcmread(os.path.join(patient_dir, f)) for f in os.listdir(patient_dir) if f.endswith('.dcm')]\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n        vol_3d = np.stack([s.pixel_array for s in slices])\n        print(f\"✅ Patient Data Loaded. Shape: {vol_3d.shape}\")\n    except Exception as e:\n        print(f\"⚠️ Could not load OSIC data ({e}). Generating synthetic noise volume...\")\n        vol_3d = np.random.randint(-1000, 200, (60, 512, 512))\n\n# --- PART 2: INJECT THE TUMORS (Time A vs Time B) ---\ndef inject_organic_tumor(volume, center, radius):\n    vol_copy = volume.copy()\n    z_c, y_c, x_c = center\n    max_val = np.max(volume) * 0.7\n    \n    box = int(radius + 5)\n    for z in range(max(0, z_c-box), min(volume.shape[0], z_c+box)):\n        for y in range(max(0, y_c-box), min(volume.shape[1], y_c+box)):\n            for x in range(max(0, x_c-box), min(volume.shape[2], x_c+box)):\n                dist = np.sqrt((z-z_c)**2 + (y-y_c)**2 + (x-x_c)**2)\n                noise = random.uniform(-1.5, 1.5)\n                if dist < (radius + noise):\n                    vol_copy[z, y, x] = max_val + random.uniform(-50, 50)\n    return vol_copy\n\n# Coordinates (Right Lung)\nz_mid = len(vol_3d) // 2\ny_mid = vol_3d.shape[1] // 2\nx_mid = vol_3d.shape[2] // 4\ntumor_coords = (z_mid, y_mid, x_mid + 50)\n\nprint(\"💉 Injecting Tumors (Baseline vs Progression)...\")\nscan_a_real = inject_organic_tumor(vol_3d, tumor_coords, radius=6) # 6mm\nscan_b_real = inject_organic_tumor(vol_3d, tumor_coords, radius=9) # 9mm (Growth)\n\n# --- PART 3: AI ANALYSIS ---\n# Calculate Math\nvol_a_math = (4/3) * np.pi * (6**3)\nvol_b_math = (4/3) * np.pi * (9**3)\ngrowth_pct = ((vol_b_math - vol_a_math) / vol_a_math) * 100\n\nprint(\"🧠 MedGemma is generating the report...\")\n\nprompt = f\"\"\"<start_of_turn>user\nYou are an expert Thoracic Radiologist using RECIST 1.1 criteria.\nGenerate a concise clinical report.\n\nPatient ID: OSIC-001 (Fibrosis)\nFindings:\n- Baseline Volume: {vol_a_math:.0f} mm3\n- Current Volume: {vol_b_math:.0f} mm3\n- Change: {growth_pct:+.1f}%\n- Morphology: Spiculated nodule in fibrotic lung.\n\nTask:\n1. State findings.\n2. Classify (Stable vs Progressive).\n3. Recommend next steps.\n<end_of_turn>\n<start_of_turn>model\n\"\"\"\n\ninputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\noutputs = model.generate(**inputs, max_new_tokens=250, temperature=0.7, do_sample=True)\nreport = tokenizer.decode(outputs[0], skip_special_tokens=True).split(\"model\")[-1].strip()\n\n# --- PART 4: VISUALIZATION DASHBOARD ---\nprint(\"\\n\" + \"=\"*60)\nprint(\"             🏥 CHRONOSCAN DIAGNOSTIC DASHBOARD\")\nprint(\"=\"*60)\nprint(f\"\\n{report}\\n\")\nprint(\"-\" * 60)\n\nplt.figure(figsize=(14, 6))\n# Plot A\nplt.subplot(1, 2, 1)\nplt.imshow(scan_a_real[tumor_coords[0]], cmap='gray')\nplt.title(f\"Baseline (Time A)\\nVol: {vol_a_math:.0f} mm3\")\nc1 = plt.Circle((tumor_coords[2], tumor_coords[1]), 25, color='red', fill=False, linewidth=2)\nplt.gca().add_patch(c1)\nplt.axis('off')\n\n# Plot B\nplt.subplot(1, 2, 2)\nplt.imshow(scan_b_real[tumor_coords[0]], cmap='gray')\nplt.title(f\"Follow-up (Time B)\\nVol: {vol_b_math:.0f} mm3 (+{growth_pct:.0f}%)\")\nc2 = plt.Circle((tumor_coords[2], tumor_coords[1]), 30, color='red', fill=False, linewidth=2)\nplt.gca().add_patch(c2)\nplt.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# PHASE 7: PROFESSIONAL MEDICAL REPORTING\n# ==========================================\n\nimport torch\n\n# 1. SETUP THE MATH (Same as before)\nvol_a_math = (4/3) * np.pi * (6**3)  # 905 mm3\nvol_b_math = (4/3) * np.pi * (9**3)  # 3054 mm3\ngrowth_pct = ((vol_b_math - vol_a_math) / vol_a_math) * 100\n\nprint(\"🧠 MedGemma is generating a FULL CONSULTANT REPORT...\")\n\n# 2. THE ADVANCED PROMPT\n# We use \"Few-Shot\" formatting to force the style we want.\nprompt = f\"\"\"<start_of_turn>user\nYou are a Senior Consultant Thoracic Radiologist.\nWrite a comprehensive \"High-Resolution Chest CT Follow-up Report\" for a patient with Pulmonary Fibrosis.\n\nPatient Data:\n- ID: OSIC-001\n- Indication: Evaluation of pulmonary nodule progression.\n- Prior Scan: 6 Months ago.\n\nQuantitative Analysis (ChronoScan 3D):\n- Baseline Volume: {vol_a_math:.0f} mm3\n- Current Volume: {vol_b_math:.0f} mm3\n- Net Change: {growth_pct:+.1f}%\n- Morphology: Solid, spiculated margins suggestive of malignancy.\n- Background: Diffuse reticular opacities consistent with fibrosis.\n\nInstructions:\n1. Use professional medical terminology.\n2. Structure the report with these headers: **CLINICAL INDICATION**, **TECHNIQUE**, **COMPARATIVE FINDINGS**, **IMPRESSION**.\n3. In the IMPRESSION, explicitly reference RECIST 1.1 criteria (growth >20% indicates Progressive Disease).\n4. Provide a strong recommendation (e.g., PET-CT, Biopsy, Multidisciplinary Team Meeting).\n\nReport:<end_of_turn>\n<start_of_turn>model\n\"\"\"\n\n# 3. GENERATE (Longer Token Limit)\n# We allow 600 tokens now so it can write a full page\ninputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n\noutputs = model.generate(\n    **inputs, \n    max_new_tokens=600,   # Increased from 250\n    temperature=0.7,      # Standard creativity\n    repetition_penalty=1.1, # Prevents it from repeating \"The tumor... the tumor...\"\n    do_sample=True\n)\n\nresponse = tokenizer.decode(outputs[0], skip_special_tokens=True)\nreport_body = response.split(\"model\")[-1].strip()\n\n# 4. DISPLAY\nprint(\"\\n\" + \"=\"*60)\nprint(\"             🏥 CHRONOSCAN OFFICIAL RADIOLOGY REPORT\")\nprint(\"=\"*60)\nprint(report_body)\nprint(\"=\"*60)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Install libraries (Removed strict versioning to fix the Hugging Face conflict)\n!pip install -q -U transformers accelerate bitsandbytes huggingface_hub\n\nimport torch\nfrom transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig\nfrom huggingface_hub import login\n\n# 2. Login\nHF_TOKEN = \"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\" \ntry:\n    login(token=HF_TOKEN)\nexcept:\n    pass\n\n# 3. Load Model\nMODEL_ID = \"google/gemma-2b-it\"\nprint(f\"🧠 Waking up {MODEL_ID}...\")\n\nbnb_config = BitsAndBytesConfig(\n        load_in_4bit=True,\n        bnb_4bit_quant_type=\"nf4\",\n        bnb_4bit_compute_dtype=torch.float16,\n)\n\ntokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN)\nmodel = AutoModelForCausalLM.from_pretrained(\n    MODEL_ID,\n    quantization_config=bnb_config,\n    device_map=\"auto\",\n    token=HF_TOKEN\n)\nprint(\"✅ SUCCESS! The MedGemma model is now in memory.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T08:03:48.815172Z","iopub.execute_input":"2026-02-20T08:03:48.815901Z","iopub.status.idle":"2026-02-20T08:04:38.672569Z","shell.execute_reply.started":"2026-02-20T08:03:48.81587Z","shell.execute_reply":"2026-02-20T08:04:38.67176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# PHASE 8: THE CHRONOSCAN WEB INTERFACE (GRADIO)\n# ==========================================\n\nimport os\nimport random\nimport pydicom\nimport gradio as gr\nimport torch\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib.patches import Rectangle\nfrom matplotlib.backends.backend_pdf import PdfPages\nimport textwrap\nimport io\nimport traceback\nfrom PIL import Image\n\n# --- 1. HELPER FUNCTIONS ---\ndef create_scan_image(scan_slice, x_c, y_c, radius, title):\n    \"\"\"Draws the scan with a red circle and converts it to an image for the UI.\"\"\"\n    fig, ax = plt.subplots(figsize=(6, 6))\n    ax.imshow(scan_slice, cmap='gray')\n    \n    # Draw Red Circle (scales dynamically with the tumor radius)\n    circle = plt.Circle((x_c, y_c), radius, color='red', fill=False, linewidth=3)\n    ax.add_patch(circle)\n    \n    # Styling\n    ax.set_title(title, color='white', fontsize=14, pad=10)\n    ax.axis('off')\n    fig.patch.set_facecolor('#0f172a') # Deep Slate background to match theme\n    \n    # Save to memory buffer\n    buf = io.BytesIO()\n    plt.savefig(buf, format='png', bbox_inches='tight', facecolor='#0f172a')\n    buf.seek(0)\n    img = Image.open(buf)\n    plt.close(fig)\n    return img\n\ndef inject_organic_tumor(volume, center, radius):\n    \"\"\"Injects a realistic 3D tumor into the scan volume.\"\"\"\n    vol_copy = volume.copy()\n    z_c, y_c, x_c = center\n    max_val = np.max(volume) * 0.7\n    \n    box = int(radius + 5)\n    for z in range(max(0, z_c-box), min(volume.shape[0], z_c+box)):\n        for y in range(max(0, y_c-box), min(volume.shape[1], y_c+box)):\n            for x in range(max(0, x_c-box), min(volume.shape[2], x_c+box)):\n                dist = np.sqrt((z-z_c)**2 + (y-y_c)**2 + (x-x_c)**2)\n                noise = random.uniform(-1.5, 1.5)\n                if dist < (radius + noise):\n                    vol_copy[z, y, x] = max_val + random.uniform(-50, 50)\n    return vol_copy\n\ndef draw_pdf_header(fig, title_text):\n    \"\"\"Draws a premium dark medical banner at the top of the PDF page.\"\"\"\n    # Dark slate background banner\n    fig.patches.append(Rectangle((0, 0.88), 1, 0.12, transform=fig.transFigure, color='#0f172a', zorder=1))\n    # Logo text\n    fig.text(0.08, 0.94, \"⚕️ ChronoScan AI\", color='#38bdf8', fontsize=22, weight='bold', family='sans-serif')\n    fig.text(0.08, 0.915, \"ADVANCED LONGITUDINAL 3D TUMOR TRACKING\", color='#94a3b8', fontsize=10, family='sans-serif')\n    # Right-aligned page title\n    fig.text(0.92, 0.93, title_text, color='#cbd5e1', fontsize=12, ha='right', va='center', weight='bold', family='sans-serif')\n\n# --- 2. THE MAIN ANALYSIS FUNCTION (Triggered by Button) ---\ndef run_chronoscan():\n    print(\"UI Button Clicked: Running Dynamic Analysis...\")\n    \n    try:\n        # --- SAFETY CHECK: Verify Model is Loaded ---\n        if 'model' not in globals() or 'tokenizer' not in globals():\n            return (None, None, \n                    \"❌ ERROR: MODEL NOT FOUND\\n\\nThe MedGemma model is not in memory. Please run the cell that loads the model first!\", \n                    \"\", None)\n\n        # --- LOAD DATA (Once per session) ---\n        global vol_3d, tumor_coords\n        \n        if 'vol_3d' not in globals():\n            print(\"📦 Reloading Patient Data from OSIC Dataset...\")\n            DATA_PATH = \"/kaggle/input/osic-pulmonary-fibrosis-progression/train\"\n            try:\n                patients = os.listdir(DATA_PATH)\n                target_patient = patients[0] # Pick first patient\n                patient_dir = os.path.join(DATA_PATH, target_patient)\n                \n                slices = [pydicom.dcmread(os.path.join(patient_dir, f)) for f in os.listdir(patient_dir) if f.endswith('.dcm')]\n                slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n                vol_3d = np.stack([s.pixel_array for s in slices])\n                print(f\"✅ Patient Data Loaded. Shape: {vol_3d.shape}\")\n            except Exception as e:\n                print(f\"⚠️ Could not load OSIC data ({e}). Generating synthetic noise volume...\")\n                vol_3d = np.random.randint(-1000, 200, (60, 512, 512))\n\n            # Coordinates (Right Lung)\n            z_mid = len(vol_3d) // 2\n            y_mid = vol_3d.shape[1] // 2\n            x_mid = vol_3d.shape[2] // 4\n            tumor_coords = (z_mid, y_mid, x_mid + 50)\n\n        # --- DYNAMIC RANDOMIZATION FOR EVERY CLICK ---\n        radius_a = round(random.uniform(4.5, 8.0), 1) \n        growth_multiplier = random.uniform(1.05, 1.6) \n        radius_b = round(radius_a * growth_multiplier, 1)\n\n        # Calculate DIAMETER for clinical accuracy (d = r * 2)\n        diameter_a = radius_a * 2\n        diameter_b = radius_b * 2\n\n        print(f\"💉 Injecting Dynamic Tumors (Time A: {diameter_a}mm | Time B: {diameter_b}mm)...\")\n        scan_a_real = inject_organic_tumor(vol_3d, tumor_coords, radius=radius_a) \n        scan_b_real = inject_organic_tumor(vol_3d, tumor_coords, radius=radius_b)\n\n        # Calculate Math dynamically\n        vol_a_math = (4/3) * np.pi * (radius_a**3)\n        vol_b_math = (4/3) * np.pi * (radius_b**3)\n        growth_pct = ((vol_b_math - vol_a_math) / vol_a_math) * 100\n        \n        # Generate Images using our dynamic variables\n        target_z = tumor_coords[0]\n        y_c, x_c = tumor_coords[1], tumor_coords[2]\n        \n        # Dynamic red circle sizing, using 'd' for Diameter\n        img_a = create_scan_image(scan_a_real[target_z], x_c, y_c, radius_a + 15, f\"Baseline (Time A) | d={diameter_a:.1f}mm\")\n        img_b = create_scan_image(scan_b_real[target_z], x_c, y_c, radius_b + 15, f\"Follow-up (Time B) | d={diameter_b:.1f}mm\")\n\n        # Generate AI Report (Advanced, Highly Elaborated Prompt)\n        prompt = f\"\"\"<start_of_turn>user\nYou are a Senior Consultant Thoracic Radiologist at a top-tier research hospital.\nWrite a highly detailed, comprehensive \"High-Resolution Chest CT Follow-up Report\" for a patient with Pulmonary Fibrosis.\n\nPatient Data:\n- ID: OSIC-001\n- Indication: Evaluation of pulmonary nodule progression in the setting of interstitial lung disease.\n- Prior Scan: 6 Months ago.\n\nQuantitative Analysis (ChronoScan 3D AI Tracking):\n- Baseline Nodule: Longest Diameter = {diameter_a:.1f} mm, Volume = {vol_a_math:.2f} mm3\n- Current Nodule: Longest Diameter = {diameter_b:.1f} mm, Volume = {vol_b_math:.2f} mm3\n- Net Volumetric Change: {growth_pct:+.1f}%\n- Morphology: Solid nodule demonstrating spiculated margins and pleural tagging, suggestive of a malignant phenotype.\n- Background: Diffuse reticular opacities and honeycombing consistent with pulmonary fibrosis.\n\nInstructions:\n1. Use highly professional medical terminology. Write a detailed, multi-paragraph report.\n2. Structure the report precisely with these bold headers: **CLINICAL INDICATION**, **TECHNIQUE**, **COMPARATIVE FINDINGS**, **IMPRESSION**, and **PROGNOSTIC OUTLOOK**.\n3. In the **COMPARATIVE FINDINGS**, elaborate extensively on the morphological features (e.g., spiculation, interaction with fibrotic tissue) and detail exactly how the {growth_pct:+.1f}% volumetric change alters the clinical picture. Reference the change in longest diameter.\n4. In the **IMPRESSION**, explicitly reference RECIST 1.1 criteria and classify as Progressive Disease if warranted by the measurements.\n5. In the **PROGNOSTIC OUTLOOK**, provide a strong, clinically justified recommendation (e.g., PET-CT, tissue biopsy, Multidisciplinary Tumor Board review) considering both the nodule growth and the underlying fibrosis.\n\nReport:<end_of_turn>\n<start_of_turn>model\n\"\"\"\n\n        inputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n        outputs = model.generate(\n            **inputs, \n            max_new_tokens=800,\n            temperature=0.75,\n            repetition_penalty=1.15,\n            do_sample=True\n        )\n        \n        response = tokenizer.decode(outputs[0], skip_special_tokens=True)\n        report_body = response.split(\"model\")[-1].strip()\n        \n        # Determine Status dynamically\n        is_critical = growth_pct > 20\n        status_alert = \"[CRITICAL] PROGRESSION DETECTED (>20% THRESHOLD)\" if is_critical else \"[STABLE] DISEASE STABLE (<20% THRESHOLD)\"\n        recist_status = \"PROGRESSIVE DISEASE.\" if is_critical else \"STABLE DISEASE.\"\n\n        # Format UI Stats Box\n        stats_text = (\n            f\" [ SYSTEM ANALYSIS COMPLETE ]\\n\"\n            f\" > BASELINE DIAMETER : {diameter_a:.1f} mm\\n\"\n            f\" > BASELINE VOLUME   : {vol_a_math:.2f} mm³\\n\"\n            f\" > CURRENT DIAMETER  : {diameter_b:.1f} mm\\n\"\n            f\" > CURRENT VOLUME    : {vol_b_math:.2f} mm³\\n\"\n            f\" > NET GROWTH        : +{growth_pct:.2f}%\\n\"\n            f\" > STATUS ALARM      : {status_alert}\\n\"\n            f\" > RECIST 1.1        : {recist_status}\"\n        )\n\n        # --- GENERATE STYLED PDF REPORT ---\n        pdf_path = \"ChronoScan_Clinical_Report.pdf\"\n        with PdfPages(pdf_path) as pdf:\n            \n            # ---------------------------------------------\n            # PAGE 1: EXECUTIVE SUMMARY & METRICS GRID\n            # ---------------------------------------------\n            fig = plt.figure(figsize=(8.5, 11))\n            fig.patch.set_facecolor('#f8fafc') # Very light slate background\n            draw_pdf_header(fig, \"EXECUTIVE SUMMARY\")\n            \n            # Patient Info Box\n            fig.patches.append(Rectangle((0.08, 0.73), 0.84, 0.1, transform=fig.transFigure, color='white', ec='#cbd5e1', lw=1.5, zorder=1))\n            fig.text(0.12, 0.79, \"PATIENT ID:\", fontsize=10, color='#64748b', weight='bold')\n            fig.text(0.28, 0.79, \"OSIC-001\", fontsize=12, color='#0f172a', weight='bold')\n            fig.text(0.55, 0.79, \"INTERVAL:\", fontsize=10, color='#64748b', weight='bold')\n            fig.text(0.70, 0.79, \"6 Months\", fontsize=12, color='#0f172a', weight='bold')\n            fig.text(0.12, 0.75, \"INDICATION:\", fontsize=10, color='#64748b', weight='bold')\n            fig.text(0.28, 0.75, \"Pulmonary Fibrosis Nodule Tracking\", fontsize=11, color='#0f172a')\n            \n            # Quantitative Metrics Box\n            fig.text(0.08, 0.67, \"3D Volumetric Tracking Metrics\", fontsize=16, color='#0f172a', weight='bold')\n            fig.patches.append(Rectangle((0.08, 0.45), 0.84, 0.2, transform=fig.transFigure, color='white', ec='#cbd5e1', lw=1.5, zorder=1))\n            \n            # Column 1\n            fig.text(0.12, 0.60, \"Baseline Diameter:\", fontsize=12, color='#64748b')\n            fig.text(0.38, 0.60, f\"{diameter_a:.1f} mm\", fontsize=12, color='#0f172a', weight='bold')\n            fig.text(0.12, 0.55, \"Baseline Volume:\", fontsize=12, color='#64748b')\n            fig.text(0.38, 0.55, f\"{vol_a_math:.2f} mm³\", fontsize=12, color='#0f172a', weight='bold')\n            \n            # Column 2\n            fig.text(0.55, 0.60, \"Follow-up Diameter:\", fontsize=12, color='#64748b')\n            fig.text(0.80, 0.60, f\"{diameter_b:.1f} mm\", fontsize=12, color='#0f172a', weight='bold')\n            fig.text(0.55, 0.55, \"Follow-up Volume:\", fontsize=12, color='#64748b')\n            fig.text(0.80, 0.55, f\"{vol_b_math:.2f} mm³\", fontsize=12, color='#0f172a', weight='bold')\n            \n            # Divider Line\n            fig.patches.append(Rectangle((0.12, 0.52), 0.76, 0.002, transform=fig.transFigure, color='#e2e8f0', zorder=2))\n            \n            # Net Growth\n            growth_color = '#dc2626' if is_critical else '#16a34a' # Red if bad, Green if good\n            fig.text(0.12, 0.48, \"NET VOLUMETRIC GROWTH:\", fontsize=12, color='#0f172a', weight='bold')\n            fig.text(0.55, 0.48, f\"+{growth_pct:.2f}%\", fontsize=16, color=growth_color, weight='heavy')\n\n            # Status Alert Box (Dynamic Color)\n            alert_bg = '#fef2f2' if is_critical else '#f0fdf4'\n            alert_border = '#f87171' if is_critical else '#4ade80'\n            fig.patches.append(Rectangle((0.08, 0.30), 0.84, 0.12, transform=fig.transFigure, color=alert_bg, ec=alert_border, lw=2, zorder=1))\n            fig.text(0.5, 0.38, \"CLINICAL STATUS ALARM\", fontsize=10, color=growth_color, ha='center', weight='bold')\n            fig.text(0.5, 0.34, status_alert, fontsize=14, color=growth_color, ha='center', weight='bold')\n\n            pdf.savefig(fig)\n            plt.close(fig)\n            \n            # ---------------------------------------------\n            # PAGE 2: RADIOLOGICAL IMAGES\n            # ---------------------------------------------\n            fig = plt.figure(figsize=(8.5, 11))\n            fig.patch.set_facecolor('#f8fafc')\n            draw_pdf_header(fig, \"RADIOLOGICAL EVIDENCE\")\n            \n            # Render Baseline Image\n            ax1 = fig.add_axes([0.2, 0.48, 0.6, 0.35]) # Centered Top\n            ax1.imshow(scan_a_real[target_z], cmap='gray')\n            ax1.add_patch(plt.Circle((x_c, y_c), radius_a + 15, color='#ef4444', fill=False, linewidth=2.5))\n            ax1.axis('off')\n            ax1.set_title(f\"Baseline Scan (Time A)\\nLD: {diameter_a:.1f} mm\", fontsize=14, pad=10, color='#0f172a', weight='bold')\n            \n            # Render Follow-up Image\n            ax2 = fig.add_axes([0.2, 0.08, 0.6, 0.35]) # Centered Bottom\n            ax2.imshow(scan_b_real[target_z], cmap='gray')\n            ax2.add_patch(plt.Circle((x_c, y_c), radius_b + 15, color='#ef4444', fill=False, linewidth=2.5))\n            ax2.axis('off')\n            ax2.set_title(f\"Follow-up Scan (Time B)\\nLD: {diameter_b:.1f} mm\", fontsize=14, pad=10, color='#0f172a', weight='bold')\n            \n            pdf.savefig(fig)\n            plt.close(fig)\n            \n            # ---------------------------------------------\n            # PAGE 3+: AI CONSULTANT REPORT (Formatted)\n            # ---------------------------------------------\n            lines = []\n            for para in report_body.split('\\n'):\n                # Wrap text to fit page width\n                wrapped = textwrap.wrap(para, width=80)\n                if not wrapped:\n                    lines.append(\"\") # Keep paragraph breaks\n                else:\n                    lines.extend(wrapped)\n            \n            max_lines_per_page = 40\n            for i in range(0, len(lines), max_lines_per_page):\n                fig = plt.figure(figsize=(8.5, 11))\n                fig.patch.set_facecolor('#f8fafc')\n                draw_pdf_header(fig, \"MEDGEMMA AI IMPRESSION\")\n                \n                y_start = 0.82\n                for j, line in enumerate(lines[i:i+max_lines_per_page]):\n                    # Check if line has Markdown Bold (**Text**)\n                    if \"**\" in line:\n                        clean_line = line.replace(\"**\", \"\")\n                        # Render bold headers in darker, bolder font\n                        fig.text(0.08, y_start - (j*0.018), clean_line, fontsize=11, color='#0f172a', weight='bold', family='sans-serif')\n                    else:\n                        # Render normal text\n                        fig.text(0.08, y_start - (j*0.018), line, fontsize=11, color='#334155', family='sans-serif')\n                \n                # Page Number Footer\n                fig.text(0.5, 0.05, f\"Page {3 + (i // max_lines_per_page)} - ChronoScan Automated Report\", ha='center', fontsize=9, color='#94a3b8')\n                \n                pdf.savefig(fig)\n                plt.close(fig)\n        \n        return img_a, img_b, stats_text, report_body, pdf_path\n\n    except Exception as e:\n        error_details = traceback.format_exc()\n        print(\"❌ ERROR CAUGHT:\\n\", error_details)\n        return (None, None, \n                \"❌ SYSTEM FAILURE\\n\\nAn error occurred in the Python backend:\\n\\n\" + error_details, \n                \"\", None)\n\n# --- 3. CUSTOM CSS FOR STYLING ---\ncustom_css = \"\"\"\n@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@500;800&family=Inter:wght@400;600&display=swap');\n\nbody {\n    background-color: #020617; /* Very dark slate */\n}\n\n/* Glowing Title */\n.main-title {\n    font-family: 'Orbitron', sans-serif;\n    text-align: center;\n    background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);\n    -webkit-background-clip: text;\n    -webkit-text-fill-color: transparent;\n    font-size: 3.5rem;\n    font-weight: 800;\n    text-shadow: 0px 0px 15px rgba(0, 242, 254, 0.4);\n    margin-bottom: 0.1rem;\n    padding-top: 1rem;\n}\n\n/* Subtitle */\n.sub-title {\n    font-family: 'Inter', sans-serif;\n    text-align: center;\n    color: #94a3b8;\n    font-size: 1.2rem;\n    margin-bottom: 2rem;\n    letter-spacing: 1px;\n}\n\n/* Cyber Button Hover & Glow Effects */\n.glow-button {\n    background: linear-gradient(45deg, #0ea5e9, #2563eb) !important;\n    border: none !important;\n    color: white !important;\n    font-family: 'Orbitron', sans-serif !important;\n    font-weight: 800 !important;\n    font-size: 1.2rem !important;\n    letter-spacing: 1.5px !important;\n    border-radius: 8px !important;\n    box-shadow: 0 0 15px rgba(14, 165, 233, 0.5) !important;\n    transition: all 0.3s ease-in-out !important;\n    margin-top: 10px !important;\n    margin-bottom: 20px !important;\n}\n\n.glow-button:hover {\n    transform: translateY(-2px) scale(1.02) !important;\n    box-shadow: 0 0 25px rgba(14, 165, 233, 0.9) !important;\n    background: linear-gradient(45deg, #00f2fe, #4facfe) !important;\n}\n\n/* Terminal-style Metrics Box - RED ALERT STYLING */\n.terminal-box textarea {\n    color: #ff4444 !important; /* Neon Red text */\n    font-family: 'Courier New', Courier, monospace !important;\n    font-size: 1.1rem !important;\n    font-weight: 600 !important;\n    background-color: #1a0505 !important; /* Very dark red/black */\n    border: 1px solid #7f1d1d !important; /* Dark red border */\n    box-shadow: inset 0 0 15px rgba(0,0,0,0.8) !important;\n    border-radius: 5px !important;\n}\n\n/* Report Box Styling */\n.report-box textarea {\n    font-family: 'Inter', sans-serif !important;\n    font-size: 1.05rem !important;\n    line-height: 1.6 !important;\n    background-color: #1e293b !important;\n    border: 1px solid #334155 !important;\n    color: #f1f5f9 !important;\n}\n\"\"\"\n\n# --- 4. BUILD THE GRADIO WEB INTERFACE ---\nwith gr.Blocks(theme=gr.themes.Base(), css=custom_css) as app:\n    \n    # Custom HTML Header\n    gr.HTML('''\n        <div class=\"main-title\">⚕️ ChronoScan AI</div>\n        <div class=\"sub-title\">Advanced Longitudinal 3D Tumor Tracking via Edge-AI</div>\n    ''')\n    \n    # Image Viewer Section\n    with gr.Row():\n        with gr.Column():\n            image_a = gr.Image(label=\"Scan Time A (Baseline)\", type=\"pil\", interactive=False)\n        with gr.Column():\n            image_b = gr.Image(label=\"Scan Time B (6 Months Later)\", type=\"pil\", interactive=False)\n            \n    # Interactive Button\n    analyze_btn = gr.Button(\"🚀 INITIATE MEDGEMMA NEURAL ANALYSIS\", elem_classes=[\"glow-button\"], size=\"lg\")\n    \n    # Tabbed Outputs for better UI layout\n    with gr.Tabs():\n        with gr.TabItem(\"📊 Volumetric Metrics Data\"):\n            stats_box = gr.Textbox(\n                label=\"3D Quantitative Analysis Log\", \n                lines=10, \n                elem_classes=[\"terminal-box\"]\n            )\n            \n        with gr.TabItem(\"🩺 AI Consultant Report\"):\n            report_box = gr.Textbox(\n                label=\"MedGemma Final Output\", \n                lines=20,\n                elem_classes=[\"report-box\"]\n            )\n            \n    # PDF Download Section\n    with gr.Row():\n        pdf_download = gr.File(label=\"📄 Download Premium Styled PDF Report\", interactive=False)\n\n    # Wire up the button to our function\n    analyze_btn.click(\n        fn=run_chronoscan,\n        inputs=[],\n        outputs=[image_a, image_b, stats_box, report_box, pdf_download]\n    )\n\n# --- 5. LAUNCH THE APP ---\nprint(\"🚀 Launching Enhanced Web App...\")\napp.launch(share=True, debug=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-20T08:04:50.538985Z","iopub.execute_input":"2026-02-20T08:04:50.540085Z","iopub.status.idle":"2026-02-20T08:04:55.861866Z","shell.execute_reply.started":"2026-02-20T08:04:50.540049Z","shell.execute_reply":"2026-02-20T08:04:55.861303Z"}},"outputs":[],"execution_count":null}]}