{"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":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg\n!pip install nibabel pandas numpy kaggle","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\"\"\"\nRSNA Intracranial Aneurysm Detection - CTA to NIfTI Downloader\nDownloads DICOM series and converts to NIfTI (.nii.gz) 3D volumes\n\"\"\"\n\n# ============================================\n# INSTALLATION\n# ============================================\n# !pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg\n# !pip install nibabel pandas numpy kaggle\n\nimport pandas as pd\nimport numpy as np\nimport os\nfrom pathlib import Path\n\n# ============================================\n# STEP 1: Filter CTA Series from train.csv\n# ============================================\n\ndef load_and_filter_cta(csv_path='train.csv'):\n    \"\"\"Load train.csv and filter for CTA modality\"\"\"\n    df = pd.read_csv(csv_path)\n    \n    print(f\"Total series: {len(df)}\")\n    print(f\"\\nModality distribution:\")\n    print(df['Modality'].value_counts())\n    \n    cta_df = df[df['Modality'] == 'CTA'].copy()\n    print(f\"\\nCTA series: {len(cta_df)}\")\n    \n    return cta_df\n\ndef select_cta_samples(cta_df, n_samples=5, with_aneurysm=None):\n    \"\"\"Select n CTA samples\"\"\"\n    if with_aneurysm is not None:\n        filtered_df = cta_df[cta_df['Aneurysm Present'] == int(with_aneurysm)]\n    else:\n        filtered_df = cta_df\n    \n    selected = filtered_df.sample(n=min(n_samples, len(filtered_df)), random_state=42)\n    \n    print(f\"\\nSelected {len(selected)} CTA series:\")\n    print(\"-\" * 70)\n    for _, row in selected.iterrows():\n        status = \"✓ ANEURYSM\" if row['Aneurysm Present'] == 1 else \"No aneurysm\"\n        print(f\"{row['SeriesInstanceUID'][:45]}...\")\n        print(f\"  → {row['PatientAge']}yo {row['PatientSex']}, {status}\\n\")\n    \n    return selected\n\n# ============================================\n# STEP 2: DICOM to NIfTI Conversion\n# ============================================\n\ndef load_dicom_series(dicom_folder):\n    \"\"\"\n    Load DICOM series using pydicom with JPEG support\n    Requires: pydicom, pylibjpeg, pylibjpeg-libjpeg, pylibjpeg-openjpeg\n    \"\"\"\n    import pydicom\n    \n    # Get all DICOM files\n    dcm_files = list(Path(dicom_folder).glob(\"*.dcm\"))\n    if len(dcm_files) == 0:\n        dcm_files = list(Path(dicom_folder).glob(\"*\"))  # Try without extension\n        dcm_files = [f for f in dcm_files if f.is_file()]\n    \n    if len(dcm_files) == 0:\n        print(f\"  ⚠ No DICOM files found in {dicom_folder}\")\n        return None, None\n    \n    print(f\"  Found {len(dcm_files)} DICOM files\")\n    \n    # Read all slices\n    slices = []\n    for dcm_path in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(dcm_path))\n            slices.append(ds)\n        except Exception as e:\n            print(f\"  ⚠ Could not read {dcm_path.name}: {e}\")\n    \n    if len(slices) == 0:\n        return None, None\n    \n    # Sort slices by z-position or instance number\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except:\n        try:\n            slices.sort(key=lambda x: int(x.InstanceNumber))\n        except:\n            pass  # Keep original order\n    \n    # Stack pixel arrays into 3D volume\n    volume = np.stack([s.pixel_array for s in slices], axis=-1)\n    \n    # Apply rescale slope/intercept for Hounsfield units\n    first = slices[0]\n    slope = float(getattr(first, 'RescaleSlope', 1))\n    intercept = float(getattr(first, 'RescaleIntercept', 0))\n    volume = volume.astype(np.float32) * slope + intercept\n    \n    print(f\"  Volume shape: {volume.shape}\")\n    \n    return volume, slices\n\ndef dicom_to_nifti(dicom_folder, output_path):\n    \"\"\"\n    Convert DICOM series to NIfTI using pydicom + nibabel\n    Handles JPEG-compressed DICOM with pylibjpeg\n    \"\"\"\n    import nibabel as nib\n    \n    # Load DICOM series\n    volume, slices = load_dicom_series(dicom_folder)\n    \n    if volume is None:\n        return None\n    \n    first = slices[0]\n    \n    # Get pixel spacing\n    pixel_spacing = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n    \n    # Calculate slice spacing\n    if len(slices) > 1:\n        try:\n            pos1 = np.array([float(x) for x in slices[0].ImagePositionPatient])\n            pos2 = np.array([float(x) for x in slices[1].ImagePositionPatient])\n            slice_spacing = abs(np.linalg.norm(pos2 - pos1))\n        except:\n            slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    else:\n        slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    \n    # Build affine matrix\n    affine = np.eye(4)\n    \n    try:\n        # Get orientation from ImageOrientationPatient\n        iop = [float(x) for x in first.ImageOrientationPatient]\n        row_cosine = np.array(iop[:3])\n        col_cosine = np.array(iop[3:])\n        slice_cosine = np.cross(row_cosine, col_cosine)\n        \n        # Build rotation matrix\n        affine[:3, 0] = row_cosine * pixel_spacing[0]\n        affine[:3, 1] = col_cosine * pixel_spacing[1]\n        affine[:3, 2] = slice_cosine * slice_spacing\n        \n        # Set origin\n        origin = [float(x) for x in first.ImagePositionPatient]\n        affine[:3, 3] = origin\n    except:\n        # Fallback: simple diagonal affine\n        affine[0, 0] = pixel_spacing[0]\n        affine[1, 1] = pixel_spacing[1]\n        affine[2, 2] = slice_spacing\n    \n    print(f\"  Spacing: {pixel_spacing[0]:.2f} x {pixel_spacing[1]:.2f} x {slice_spacing:.2f} mm\")\n    \n    # Create NIfTI image\n    nifti_img = nib.Nifti1Image(volume.astype(np.int16), affine)\n    \n    # Save as compressed NIfTI\n    nib.save(nifti_img, str(output_path))\n    print(f\"  ✓ Saved: {output_path}\")\n    \n    return nifti_img\n\n# ============================================\n# STEP 3: Download & Convert Pipeline\n# ============================================\n\ndef download_and_convert_to_nifti(series_uids, output_dir='cta_nifti'):\n    \"\"\"\n    Download DICOM series from Kaggle and convert to NIfTI\n    \"\"\"\n    from kaggle.api.kaggle_api_extended import KaggleApi\n    import tempfile\n    import shutil\n    \n    os.makedirs(output_dir, exist_ok=True)\n    \n    api = KaggleApi()\n    api.authenticate()\n    \n    # UPDATE with correct competition name\n    COMPETITION = 'rsna-2023-abdominal-trauma-detection'\n    \n    print(f\"\\nDownloading {len(series_uids)} CTA series → NIfTI\")\n    print(\"=\" * 70)\n    \n    converted = []\n    \n    for i, series_uid in enumerate(series_uids, 1):\n        print(f\"\\n[{i}/{len(series_uids)}] {series_uid[:45]}...\")\n        \n        temp_dir = tempfile.mkdtemp()\n        \n        try:\n            # Download from Kaggle\n            api.competition_download_file(\n                competition=COMPETITION,\n                file_name=f\"train_images/{series_uid}\",\n                path=temp_dir,\n                quiet=True\n            )\n            \n            # Find DICOM folder\n            dicom_folder = Path(temp_dir) / \"train_images\" / series_uid\n            if not dicom_folder.exists():\n                dicom_folder = Path(temp_dir) / series_uid\n            if not dicom_folder.exists():\n                dicom_folder = Path(temp_dir)\n            \n            # Convert to NIfTI\n            output_path = Path(output_dir) / f\"{series_uid}.nii.gz\"\n            result = dicom_to_nifti(dicom_folder, output_path)\n            \n            if result is not None:\n                converted.append(output_path)\n                \n        except Exception as e:\n            print(f\"  ✗ Error: {e}\")\n        finally:\n            shutil.rmtree(temp_dir, ignore_errors=True)\n    \n    print(f\"\\n{'=' * 70}\")\n    print(f\"✓ Converted {len(converted)}/{len(series_uids)} series to NIfTI\")\n    print(f\"Output: {output_dir}/\")\n    \n    return converted\n\ndef convert_local_dicom_to_nifti(dicom_base_dir, output_dir='cta_nifti'):\n    \"\"\"\n    Convert local DICOM folders to NIfTI\n    \n    Structure:\n    dicom_base_dir/\n    ├── {SeriesUID1}/*.dcm\n    └── {SeriesUID2}/*.dcm\n    \"\"\"\n    os.makedirs(output_dir, exist_ok=True)\n    \n    series_folders = [f for f in Path(dicom_base_dir).iterdir() if f.is_dir()]\n    print(f\"Found {len(series_folders)} series to convert\\n\")\n    \n    converted = []\n    \n    for i, series_folder in enumerate(series_folders, 1):\n        series_uid = series_folder.name\n        print(f\"[{i}/{len(series_folders)}] {series_uid[:45]}...\")\n        \n        output_path = Path(output_dir) / f\"{series_uid}.nii.gz\"\n        \n        try:\n            result = dicom_to_nifti(series_folder, output_path)\n            if result is not None:\n                converted.append(output_path)\n        except Exception as e:\n            print(f\"  ✗ Error: {e}\")\n    \n    print(f\"\\n✓ Converted {len(converted)}/{len(series_folders)} series\")\n    return converted\n\n# ============================================\n# STEP 4: Verify & Visualize NIfTI\n# ============================================\n\ndef verify_nifti(nifti_path):\n    \"\"\"Load and display NIfTI info\"\"\"\n    import nibabel as nib\n    \n    img = nib.load(str(nifti_path))\n    data = img.get_fdata()\n    \n    print(f\"\\n{'=' * 50}\")\n    print(f\"NIfTI: {Path(nifti_path).name}\")\n    print(f\"{'=' * 50}\")\n    print(f\"Shape:      {data.shape}\")\n    print(f\"Voxel size: {np.array(img.header.get_zooms()).round(2)} mm\")\n    print(f\"Data type:  {data.dtype}\")\n    print(f\"HU range:   [{data.min():.0f}, {data.max():.0f}]\")\n    \n    return img, data\n\ndef visualize_nifti(nifti_path, n_slices=9, save_path='cta_preview.png'):\n    \"\"\"Visualize axial slices from NIfTI\"\"\"\n    import nibabel as nib\n    import matplotlib.pyplot as plt\n    \n    img = nib.load(str(nifti_path))\n    data = img.get_fdata()\n    \n    # CTA window: center=400, width=1500\n    windowed = np.clip(data, -350, 1150)\n    windowed = ((windowed + 350) / 1500 * 255).astype(np.uint8)\n    \n    # Select evenly spaced axial slices\n    indices = np.linspace(0, data.shape[2]-1, n_slices, dtype=int)\n    \n    fig, axes = plt.subplots(3, 3, figsize=(12, 12))\n    for ax, idx in zip(axes.flat, indices):\n        ax.imshow(windowed[:, :, idx].T, cmap='gray', origin='lower')\n        ax.set_title(f'Slice {idx}/{data.shape[2]}')\n        ax.axis('off')\n    \n    plt.suptitle(f'CTA: {Path(nifti_path).stem[:40]}...', fontsize=12)\n    plt.tight_layout()\n    plt.savefig(save_path, dpi=150, bbox_inches='tight')\n    plt.show()\n    print(f\"Saved: {save_path}\")\n\n# ============================================\n# MAIN\n# ============================================\n\nif __name__ == \"__main__\":\n    # Configuration\n    CSV_PATH = 'train.csv'\n    N_SAMPLES = 5\n    OUTPUT_DIR = 'cta_nifti'\n    \n    print(\"=\" * 70)\n    print(\"RSNA Intracranial Aneurysm - CTA to NIfTI Converter\")\n    print(\"=\" * 70)\n    \n    # Step 1: Filter and select CTA series\n    cta_df = load_and_filter_cta(CSV_PATH)\n    \n    if len(cta_df) > 0:\n        selected = select_cta_samples(cta_df, n_samples=N_SAMPLES)\n        series_uids = selected['SeriesInstanceUID'].tolist()\n        \n        # Save metadata\n        selected.to_csv('selected_cta_metadata.csv', index=False)\n        print(f\"\\nMetadata saved: selected_cta_metadata.csv\")\n        \n        # Print series UIDs\n        print(\"\\n\" + \"-\" * 70)\n        print(\"Series UIDs to download:\")\n        for uid in series_uids:\n            print(f\"  {uid}\")\n        \n        print(\"\\n\" + \"=\" * 70)\n        print(\"TO CONVERT:\")\n        print(\"=\" * 70)\n        print(\"\"\"\n# Option 1: Download from Kaggle & convert\ndownload_and_convert_to_nifti(series_uids, output_dir='cta_nifti')\n\n# Option 2: Convert local DICOM folders  \nconvert_local_dicom_to_nifti('path/to/dicoms', output_dir='cta_nifti')\n\n# Verify & visualize\nverify_nifti('cta_nifti/series.nii.gz')\nvisualize_nifti('cta_nifti/series.nii.gz')\n\"\"\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 2: Run AFTER restarting kernel\n# ============================================\n\nimport pandas as pd\nimport numpy as np\nimport os\nfrom pathlib import Path\nimport pydicom\nimport nibabel as nib\nimport matplotlib.pyplot as plt\n\nprint(f\"NumPy version: {np.__version__}\")\nprint(f\"PyDICOM version: {pydicom.__version__}\")\nprint(\"✓ All imports successful!\")\n\n# ============================================\n# Filter CTA from train.csv\n# ============================================\n\ndef load_and_filter_cta(csv_path='train.csv'):\n    \"\"\"Load train.csv and filter for CTA modality\"\"\"\n    df = pd.read_csv(csv_path)\n    \n    print(f\"Total series: {len(df)}\")\n    print(f\"\\nModality distribution:\")\n    print(df['Modality'].value_counts())\n    \n    cta_df = df[df['Modality'] == 'CTA'].copy()\n    print(f\"\\nCTA series: {len(cta_df)}\")\n    \n    return cta_df\n\ndef select_cta_samples(cta_df, n_samples=5, with_aneurysm=None):\n    \"\"\"Select n CTA samples\"\"\"\n    if with_aneurysm is not None:\n        filtered_df = cta_df[cta_df['Aneurysm Present'] == int(with_aneurysm)]\n    else:\n        filtered_df = cta_df\n    \n    selected = filtered_df.sample(n=min(n_samples, len(filtered_df)), random_state=42)\n    \n    print(f\"\\nSelected {len(selected)} CTA series:\")\n    for _, row in selected.iterrows():\n        status = \"✓ ANEURYSM\" if row['Aneurysm Present'] == 1 else \"No aneurysm\"\n        print(f\"  {row['SeriesInstanceUID'][:40]}... | {row['PatientAge']}yo {row['PatientSex']} | {status}\")\n    \n    return selected\n\n# ============================================\n# DICOM to NIfTI Conversion\n# ============================================\n\ndef load_dicom_series(dicom_folder):\n    \"\"\"Load DICOM series with JPEG support\"\"\"\n    dcm_files = list(Path(dicom_folder).glob(\"*.dcm\"))\n    if len(dcm_files) == 0:\n        dcm_files = [f for f in Path(dicom_folder).iterdir() if f.is_file()]\n    \n    if len(dcm_files) == 0:\n        print(f\"  ⚠ No DICOM files found\")\n        return None, None\n    \n    print(f\"  Loading {len(dcm_files)} DICOM files...\")\n    \n    slices = []\n    for dcm_path in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(dcm_path))\n            slices.append(ds)\n        except Exception as e:\n            pass\n    \n    if len(slices) == 0:\n        return None, None\n    \n    # Sort by z-position\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except:\n        try:\n            slices.sort(key=lambda x: int(x.InstanceNumber))\n        except:\n            pass\n    \n    # Stack into volume\n    volume = np.stack([s.pixel_array for s in slices], axis=-1)\n    \n    # Apply rescale\n    first = slices[0]\n    slope = float(getattr(first, 'RescaleSlope', 1))\n    intercept = float(getattr(first, 'RescaleIntercept', 0))\n    volume = volume.astype(np.float32) * slope + intercept\n    \n    print(f\"  Volume shape: {volume.shape}\")\n    return volume, slices\n\ndef dicom_to_nifti(dicom_folder, output_path):\n    \"\"\"Convert DICOM to NIfTI\"\"\"\n    volume, slices = load_dicom_series(dicom_folder)\n    \n    if volume is None:\n        return None\n    \n    first = slices[0]\n    \n    # Spacing\n    pixel_spacing = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n    \n    if len(slices) > 1:\n        try:\n            pos1 = np.array([float(x) for x in slices[0].ImagePositionPatient])\n            pos2 = np.array([float(x) for x in slices[1].ImagePositionPatient])\n            slice_spacing = abs(np.linalg.norm(pos2 - pos1))\n        except:\n            slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    else:\n        slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    \n    # Affine matrix\n    affine = np.eye(4)\n    affine[0, 0] = pixel_spacing[0]\n    affine[1, 1] = pixel_spacing[1]\n    affine[2, 2] = slice_spacing\n    \n    try:\n        origin = [float(x) for x in first.ImagePositionPatient]\n        affine[:3, 3] = origin\n    except:\n        pass\n    \n    print(f\"  Spacing: {pixel_spacing[0]:.2f} x {pixel_spacing[1]:.2f} x {slice_spacing:.2f} mm\")\n    \n    # Save NIfTI\n    nifti_img = nib.Nifti1Image(volume.astype(np.int16), affine)\n    nib.save(nifti_img, str(output_path))\n    print(f\"  ✓ Saved: {output_path}\")\n    \n    return nifti_img\n\ndef convert_series_to_nifti(series_uids, dicom_base_dir, output_dir='cta_nifti'):\n    \"\"\"Convert multiple series to NIfTI\"\"\"\n    os.makedirs(output_dir, exist_ok=True)\n    \n    converted = []\n    for i, uid in enumerate(series_uids, 1):\n        print(f\"\\n[{i}/{len(series_uids)}] {uid[:40]}...\")\n        \n        dicom_folder = Path(dicom_base_dir) / uid\n        output_path = Path(output_dir) / f\"{uid}.nii.gz\"\n        \n        if dicom_folder.exists():\n            result = dicom_to_nifti(dicom_folder, output_path)\n            if result:\n                converted.append(output_path)\n        else:\n            print(f\"  ⚠ Folder not found: {dicom_folder}\")\n    \n    print(f\"\\n✓ Converted {len(converted)}/{len(series_uids)} series\")\n    return converted\n\n# ============================================\n# Visualization\n# ============================================\n\ndef visualize_nifti(nifti_path, n_slices=9):\n    \"\"\"Display slices from NIfTI\"\"\"\n    img = nib.load(str(nifti_path))\n    data = img.get_fdata()\n    \n    # CTA window\n    windowed = np.clip(data, -100, 700)\n    windowed = ((windowed + 100) / 800 * 255).astype(np.uint8)\n    \n    indices = np.linspace(0, data.shape[2]-1, n_slices, dtype=int)\n    \n    fig, axes = plt.subplots(3, 3, figsize=(12, 12))\n    for ax, idx in zip(axes.flat, indices):\n        ax.imshow(windowed[:, :, idx].T, cmap='gray', origin='lower')\n        ax.set_title(f'Slice {idx}')\n        ax.axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\ndef render_mip(volume):\n    \"\"\"Maximum Intensity Projection\"\"\"\n    vol_win = np.clip(volume, -100, 700)\n    \n    fig, axes = plt.subplots(1, 3, figsize=(15, 5))\n    \n    titles = ['Axial MIP', 'Coronal MIP', 'Sagittal MIP']\n    for i, ax in enumerate(axes):\n        mip = np.max(vol_win, axis=i)\n        ax.imshow(mip.T, cmap='gray', origin='lower')\n        ax.set_title(titles[i])\n        ax.axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# ============================================\n# 3D Rendering with Plotly\n# ============================================\n\ndef render_3d_plotly(volume, threshold=200, step_size=3):\n    \"\"\"3D isosurface rendering with Plotly\"\"\"\n    import plotly.graph_objects as go\n    \n    vol = volume[::step_size, ::step_size, ::step_size]\n    x, y, z = np.mgrid[0:vol.shape[0], 0:vol.shape[1], 0:vol.shape[2]]\n    \n    fig = go.Figure(data=go.Isosurface(\n        x=x.flatten(),\n        y=y.flatten(),\n        z=z.flatten(),\n        value=vol.flatten(),\n        isomin=threshold,\n        isomax=vol.max(),\n        surface_count=3,\n        colorscale='hot',\n        opacity=0.6,\n        caps=dict(x_show=False, y_show=False, z_show=False)\n    ))\n    \n    fig.update_layout(\n        title='CTA 3D Vessel Rendering',\n        scene=dict(aspectmode='data'),\n        width=800, height=800\n    )\n    fig.show()\n\n# ============================================\n# USAGE EXAMPLE\n# ============================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"USAGE EXAMPLE\")\nprint(\"=\"*60)\nprint(\"\"\"\n# 1. Filter CTA series\ncta_df = load_and_filter_cta('/kaggle/input/rsna.../train.csv')\nselected = select_cta_samples(cta_df, n_samples=5)\n\n# 2. Convert to NIfTI\nseries_uids = selected['SeriesInstanceUID'].tolist()\nconvert_series_to_nifti(series_uids, '/kaggle/input/rsna.../train_images', 'cta_nifti')\n\n# 3. Visualize\nvisualize_nifti('cta_nifti/series_uid.nii.gz')\n\n# 4. 3D rendering\nimg = nib.load('cta_nifti/series_uid.nii.gz')\nvolume = img.get_fdata()\nrender_mip(volume)\nrender_3d_plotly(volume, threshold=200)\n\"\"\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T09:33:54.5612Z","iopub.execute_input":"2026-01-05T09:33:54.5616Z","iopub.status.idle":"2026-01-05T09:34:03.863607Z","shell.execute_reply.started":"2026-01-05T09:33:54.561559Z","shell.execute_reply":"2026-01-05T09:34:03.862439Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Read single DICOM file\nds = pydicom.dcmread('/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647/1.2.826.0.1.3680043.8.498.10124807242473374136099471315028464450.dcm')\npixels = ds.pixel_array\nplt.imshow(pixels, cmap='gray')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T09:39:47.317579Z","iopub.execute_input":"2026-01-05T09:39:47.317929Z","iopub.status.idle":"2026-01-05T09:39:47.639778Z","shell.execute_reply.started":"2026-01-05T09:39:47.3179Z","shell.execute_reply":"2026-01-05T09:39:47.638843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom pathlib import Path\n\n# Series folder = parent folder of the .dcm file\nseries_folder = '/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647'\n\n# Read all DICOM files\ndcm_files = sorted(Path(series_folder).glob('*.dcm'))\nprint(f\"Found {len(dcm_files)} DICOM files\")\n\n# Load and stack into 3D volume\nslices = [pydicom.dcmread(str(f)) for f in dcm_files]\nslices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\nvolume = np.stack([s.pixel_array for s in slices], axis=0)\n\nprint(f\"3D Volume: {volume.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T09:39:30.644606Z","iopub.execute_input":"2026-01-05T09:39:30.645047Z","iopub.status.idle":"2026-01-05T09:39:35.199121Z","shell.execute_reply.started":"2026-01-05T09:39:30.645013Z","shell.execute_reply":"2026-01-05T09:39:35.198175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 1: Install packages\n# ============================================\n!pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:19:40.801245Z","iopub.execute_input":"2026-01-05T11:19:40.801574Z","iopub.status.idle":"2026-01-05T11:19:48.207893Z","shell.execute_reply.started":"2026-01-05T11:19:40.801544Z","shell.execute_reply":"2026-01-05T11:19:48.206461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 2: Import libraries\n# ============================================\nimport pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:19:48.211103Z","iopub.execute_input":"2026-01-05T11:19:48.211964Z","iopub.status.idle":"2026-01-05T11:19:50.178154Z","shell.execute_reply.started":"2026-01-05T11:19:48.211905Z","shell.execute_reply":"2026-01-05T11:19:50.177139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 3: Filter CTA from train.csv\n# ============================================\n# Load train.csv\ntrain_csv_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv'\ndf = pd.read_csv(train_csv_path)\n\nprint(f\"Total series: {len(df)}\")\nprint(f\"\\nModality distribution:\")\nprint(df['Modality'].value_counts())\n\n# Filter CTA only\ncta_df = df[df['Modality'] == 'CTA']\nprint(f\"\\nCTA series: {len(cta_df)}\")\n\n# Select 5 random CTA series\nselected_cta = cta_df.sample(n=5, random_state=42)\nprint(\"\\nSelected 5 CTA series:\")\nfor _, row in selected_cta.iterrows():\n    status = \"ANEURYSM\" if row['Aneurysm Present'] == 1 else \"Normal\"\n    print(f\"  {row['SeriesInstanceUID'][:40]}... | {row['PatientAge']}yo {row['PatientSex']} | {status}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:19:50.179307Z","iopub.execute_input":"2026-01-05T11:19:50.179734Z","iopub.status.idle":"2026-01-05T11:19:50.255974Z","shell.execute_reply.started":"2026-01-05T11:19:50.179705Z","shell.execute_reply":"2026-01-05T11:19:50.254894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 4: Function to convert DICOM to NIfTI\n# ============================================\ndef dicom_series_to_nifti(series_uid, input_base_dir, output_dir='cta_nifti'):\n    \"\"\"\n    Read DICOM series and save as NIfTI (.nii.gz)\n    \"\"\"\n    series_folder = Path(input_base_dir) / series_uid\n    \n    # Find DICOM files\n    dcm_files = sorted(series_folder.glob('*.dcm'))\n    if len(dcm_files) == 0:\n        print(f\"  ⚠ No DICOM files found in {series_folder}\")\n        return None\n    \n    print(f\"  Found {len(dcm_files)} DICOM files\")\n    \n    # Read all slices\n    slices = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(f))\n            slices.append(ds)\n        except Exception as e:\n            pass\n    \n    if len(slices) == 0:\n        return None\n    \n    # Sort by z-position\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except:\n        try:\n            slices.sort(key=lambda x: int(x.InstanceNumber))\n        except:\n            pass\n    \n    # Stack into 3D volume\n    volume = np.stack([s.pixel_array for s in slices], axis=0)\n    \n    # Apply rescale slope/intercept for Hounsfield Units\n    first = slices[0]\n    slope = float(getattr(first, 'RescaleSlope', 1))\n    intercept = float(getattr(first, 'RescaleIntercept', 0))\n    volume = volume.astype(np.float32) * slope + intercept\n    \n    # Get spacing\n    pixel_spacing = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n    \n    # Calculate slice spacing\n    if len(slices) > 1:\n        try:\n            pos1 = np.array([float(x) for x in slices[0].ImagePositionPatient])\n            pos2 = np.array([float(x) for x in slices[1].ImagePositionPatient])\n            slice_spacing = abs(np.linalg.norm(pos2 - pos1))\n        except:\n            slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    else:\n        slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    \n    # Transpose: (slices, H, W) -> (H, W, slices) for NIfTI\n    volume = np.transpose(volume, (1, 2, 0))\n    \n    # Build affine matrix\n    affine = np.eye(4)\n    affine[0, 0] = pixel_spacing[0]\n    affine[1, 1] = pixel_spacing[1]\n    affine[2, 2] = slice_spacing\n    \n    # Set origin\n    try:\n        origin = [float(x) for x in first.ImagePositionPatient]\n        affine[0, 3] = origin[0]\n        affine[1, 3] = origin[1]\n        affine[2, 3] = origin[2]\n    except:\n        pass\n    \n    print(f\"  Volume shape: {volume.shape}\")\n    print(f\"  Spacing: {pixel_spacing[0]:.2f} x {pixel_spacing[1]:.2f} x {slice_spacing:.2f} mm\")\n    \n    # Save as NIfTI\n    os.makedirs(output_dir, exist_ok=True)\n    output_path = Path(output_dir) / f\"{series_uid}.nii.gz\"\n    \n    nifti_img = nib.Nifti1Image(volume.astype(np.int16), affine)\n    nib.save(nifti_img, str(output_path))\n    \n    print(f\"  ✓ Saved: {output_path}\")\n    return output_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:19:50.25717Z","iopub.execute_input":"2026-01-05T11:19:50.257431Z","iopub.status.idle":"2026-01-05T11:19:50.274196Z","shell.execute_reply.started":"2026-01-05T11:19:50.257405Z","shell.execute_reply":"2026-01-05T11:19:50.2731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 5: Convert 5 CTA series to NIfTI\n# ============================================\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\nOUTPUT_DIR = 'cta_nifti'\n\nseries_uids = selected_cta['SeriesInstanceUID'].tolist()\n\nprint(f\"Converting {len(series_uids)} CTA series to NIfTI...\\n\")\n\nconverted_files = []\nfor i, uid in enumerate(series_uids, 1):\n    print(f\"[{i}/{len(series_uids)}] {uid[:45]}...\")\n    result = dicom_series_to_nifti(uid, INPUT_DIR, OUTPUT_DIR)\n    if result:\n        converted_files.append(result)\n    print()\n\nprint(f\"✓ Converted {len(converted_files)}/{len(series_uids)} series\")\nprint(f\"Output folder: {OUTPUT_DIR}/\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:19:50.275408Z","iopub.execute_input":"2026-01-05T11:19:50.275695Z","iopub.status.idle":"2026-01-05T11:21:53.937047Z","shell.execute_reply.started":"2026-01-05T11:19:50.275667Z","shell.execute_reply":"2026-01-05T11:21:53.935717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 6: Verify and visualize NIfTI\n# ============================================\nif converted_files:\n    # Load first NIfTI\n    nifti_path = converted_files[0]\n    img = nib.load(str(nifti_path))\n    data = img.get_fdata()\n    \n    print(f\"\\nNIfTI: {nifti_path.name}\")\n    print(f\"  Shape: {data.shape}\")\n    print(f\"  Voxel size: {img.header.get_zooms()}\")\n    print(f\"  HU range: [{data.min():.0f}, {data.max():.0f}]\")\n    \n    # Display sample slices\n    n_slices = 9\n    indices = np.linspace(0, data.shape[2]-1, n_slices, dtype=int)\n    \n    # CTA window\n    windowed = np.clip(data, -100, 700)\n    \n    fig, axes = plt.subplots(3, 3, figsize=(12, 12))\n    for ax, idx in zip(axes.flat, indices):\n        ax.imshow(windowed[:, :, idx], cmap='gray', origin='lower')\n        ax.set_title(f'Slice {idx}')\n        ax.axis('off')\n    \n    plt.suptitle(f'CTA NIfTI: {nifti_path.stem[:40]}...', fontsize=12)\n    plt.tight_layout()\n    plt.show()\n\n# ============================================\n# CELL 7: Download NIfTI files\n# ============================================\n# Option 1: Zip all NIfTI files for download\nimport shutil\n\nzip_path = shutil.make_archive('cta_nifti_5_samples', 'zip', OUTPUT_DIR)\nprint(f\"\\n✓ Created zip: {zip_path}\")\nprint(f\"  Download from Kaggle Output panel on the right →\")\n\n# Option 2: List files for individual download\nprint(\"\\nNIfTI files created:\")\nfor f in Path(OUTPUT_DIR).glob('*.nii.gz'):\n    size_mb = f.stat().st_size / (1024 * 1024)\n    print(f\"  {f.name} ({size_mb:.1f} MB)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:21:53.93838Z","iopub.execute_input":"2026-01-05T11:21:53.938762Z","iopub.status.idle":"2026-01-05T11:22:44.071826Z","shell.execute_reply.started":"2026-01-05T11:21:53.938723Z","shell.execute_reply":"2026-01-05T11:22:44.070829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport nibabel as nib\nfrom pathlib import Path\n\n# ============================================\n# STEP 1: Pick a single CTA series\n# ============================================\nseries_uid = '1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647'\nseries_folder = Path(f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{series_uid}')\n\ndcm_files = sorted(series_folder.glob('*.dcm'))\nprint(f\"STEP 1: Found {len(dcm_files)} DICOM files\")\nprint(f\"  First file: {dcm_files[0].name}\")\nprint(f\"  Last file: {dcm_files[-1].name}\")\n\n# ============================================\n# STEP 2: Read a single DICOM and inspect\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 2: Single DICOM inspection\")\nprint(\"=\"*60)\n\nds = pydicom.dcmread(str(dcm_files[0]))\n\nprint(f\"  Modality: {getattr(ds, 'Modality', 'N/A')}\")\nprint(f\"  Rows x Cols: {ds.Rows} x {ds.Columns}\")\nprint(f\"  BitsAllocated: {getattr(ds, 'BitsAllocated', 'N/A')}\")\nprint(f\"  BitsStored: {getattr(ds, 'BitsStored', 'N/A')}\")\nprint(f\"  PixelRepresentation: {getattr(ds, 'PixelRepresentation', 'N/A')}\")\nprint(f\"  RescaleSlope: {getattr(ds, 'RescaleSlope', 'N/A')}\")\nprint(f\"  RescaleIntercept: {getattr(ds, 'RescaleIntercept', 'N/A')}\")\nprint(f\"  WindowCenter: {getattr(ds, 'WindowCenter', 'N/A')}\")\nprint(f\"  WindowWidth: {getattr(ds, 'WindowWidth', 'N/A')}\")\nprint(f\"  PhotometricInterpretation: {getattr(ds, 'PhotometricInterpretation', 'N/A')}\")\nprint(f\"  PixelSpacing: {getattr(ds, 'PixelSpacing', 'N/A')}\")\nprint(f\"  SliceThickness: {getattr(ds, 'SliceThickness', 'N/A')}\")\n\n# ============================================\n# STEP 3: Raw pixel array inspection\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 3: Raw pixel array\")\nprint(\"=\"*60)\n\nraw_pixels = ds.pixel_array\nprint(f\"  Shape: {raw_pixels.shape}\")\nprint(f\"  Dtype: {raw_pixels.dtype}\")\nprint(f\"  Min: {raw_pixels.min()}\")\nprint(f\"  Max: {raw_pixels.max()}\")\nprint(f\"  Mean: {raw_pixels.mean():.2f}\")\n\n# Display raw\nfig, axes = plt.subplots(1, 3, figsize=(15, 5))\n\naxes[0].imshow(raw_pixels, cmap='gray')\naxes[0].set_title(f'Raw pixels\\nmin={raw_pixels.min()}, max={raw_pixels.max()}')\naxes[0].axis('off')\n\naxes[1].hist(raw_pixels.flatten(), bins=100)\naxes[1].set_title('Raw pixel histogram')\naxes[1].set_xlabel('Pixel value')\n\n# With auto contrast\naxes[2].imshow(raw_pixels, cmap='gray', vmin=np.percentile(raw_pixels, 1), vmax=np.percentile(raw_pixels, 99))\naxes[2].set_title('Raw with auto contrast (1-99 percentile)')\naxes[2].axis('off')\n\nplt.tight_layout()\nplt.show()\n\n# ============================================\n# STEP 4: Apply rescale slope/intercept\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 4: After rescale (Hounsfield Units)\")\nprint(\"=\"*60)\n\nslope = float(getattr(ds, 'RescaleSlope', 1))\nintercept = float(getattr(ds, 'RescaleIntercept', 0))\nprint(f\"  Slope: {slope}, Intercept: {intercept}\")\n\nhu_pixels = raw_pixels.astype(np.float32) * slope + intercept\nprint(f\"  HU Min: {hu_pixels.min():.0f}\")\nprint(f\"  HU Max: {hu_pixels.max():.0f}\")\nprint(f\"  HU Mean: {hu_pixels.mean():.2f}\")\n\n# Display HU\nfig, axes = plt.subplots(1, 3, figsize=(15, 5))\n\n# Default window\naxes[0].imshow(hu_pixels, cmap='gray')\naxes[0].set_title(f'HU (no window)\\nmin={hu_pixels.min():.0f}, max={hu_pixels.max():.0f}')\naxes[0].axis('off')\n\n# CTA window (center=400, width=1500)\ncta_win = np.clip(hu_pixels, -350, 1150)\naxes[1].imshow(cta_win, cmap='gray')\naxes[1].set_title('CTA Window (C=400, W=1500)')\naxes[1].axis('off')\n\n# Brain window (center=40, width=80)\nbrain_win = np.clip(hu_pixels, 0, 80)\naxes[2].imshow(brain_win, cmap='gray')\naxes[2].set_title('Brain Window (C=40, W=80)')\naxes[2].axis('off')\n\nplt.tight_layout()\nplt.show()\n\n# ============================================\n# STEP 5: Read all slices and check sorting\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 5: Read all slices\")\nprint(\"=\"*60)\n\nslices = []\nfor f in dcm_files:\n    ds = pydicom.dcmread(str(f))\n    slices.append(ds)\n\nprint(f\"  Loaded {len(slices)} slices\")\n\n# Check ImagePositionPatient\nprint(\"\\n  First 5 slices ImagePositionPatient[2] (z-position):\")\nfor i, s in enumerate(slices[:5]):\n    try:\n        z = float(s.ImagePositionPatient[2])\n        print(f\"    Slice {i}: z = {z:.2f}\")\n    except:\n        print(f\"    Slice {i}: No ImagePositionPatient\")\n\n# Sort by z-position\ntry:\n    slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    print(\"\\n  ✓ Sorted by ImagePositionPatient[2]\")\nexcept:\n    try:\n        slices.sort(key=lambda x: int(x.InstanceNumber))\n        print(\"\\n  ✓ Sorted by InstanceNumber\")\n    except:\n        print(\"\\n  ⚠ Could not sort slices\")\n\nprint(\"\\n  After sorting, first 5 z-positions:\")\nfor i, s in enumerate(slices[:5]):\n    try:\n        z = float(s.ImagePositionPatient[2])\n        print(f\"    Slice {i}: z = {z:.2f}\")\n    except:\n        pass\n\n# ============================================\n# STEP 6: Stack into volume\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 6: Stack into 3D volume\")\nprint(\"=\"*60)\n\n# Stack raw pixel arrays\nvolume_raw = np.stack([s.pixel_array for s in slices], axis=0)\nprint(f\"  Raw volume shape: {volume_raw.shape}\")\nprint(f\"  Raw volume dtype: {volume_raw.dtype}\")\nprint(f\"  Raw volume min: {volume_raw.min()}\")\nprint(f\"  Raw volume max: {volume_raw.max()}\")\n\n# Apply rescale\nfirst = slices[0]\nslope = float(getattr(first, 'RescaleSlope', 1))\nintercept = float(getattr(first, 'RescaleIntercept', 0))\n\nvolume_hu = volume_raw.astype(np.float32) * slope + intercept\nprint(f\"\\n  HU volume min: {volume_hu.min():.0f}\")\nprint(f\"  HU volume max: {volume_hu.max():.0f}\")\n\n# ============================================\n# STEP 7: Display middle slices from volume\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 7: Display volume slices\")\nprint(\"=\"*60)\n\nmid = volume_hu.shape[0] // 2\nprint(f\"  Middle slice index: {mid}\")\n\nfig, axes = plt.subplots(2, 3, figsize=(15, 10))\n\n# Row 1: Different slices, CTA window\nfor i, idx in enumerate([mid-50, mid, mid+50]):\n    idx = np.clip(idx, 0, volume_hu.shape[0]-1)\n    slice_data = volume_hu[idx]\n    cta_win = np.clip(slice_data, -350, 1150)\n    axes[0, i].imshow(cta_win, cmap='gray')\n    axes[0, i].set_title(f'Slice {idx} - CTA Window')\n    axes[0, i].axis('off')\n\n# Row 2: Middle slice, different windows\nslice_mid = volume_hu[mid]\n\n# Soft tissue window\nsoft_win = np.clip(slice_mid, -160, 240)\naxes[1, 0].imshow(soft_win, cmap='gray')\naxes[1, 0].set_title('Soft Tissue (C=40, W=400)')\naxes[1, 0].axis('off')\n\n# Bone window\nbone_win = np.clip(slice_mid, -200, 1800)\naxes[1, 1].imshow(bone_win, cmap='gray')\naxes[1, 1].set_title('Bone (C=800, W=2000)')\naxes[1, 1].axis('off')\n\n# Auto contrast\naxes[1, 2].imshow(slice_mid, cmap='gray', \n                   vmin=np.percentile(slice_mid, 1), \n                   vmax=np.percentile(slice_mid, 99))\naxes[1, 2].set_title('Auto Contrast (1-99%)')\naxes[1, 2].axis('off')\n\nplt.tight_layout()\nplt.show()\n\n# ============================================\n# STEP 8: Check if this is MRA vs CTA\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 8: Verify modality from CSV\")\nprint(\"=\"*60)\n\nimport pandas as pd\ntrain_csv = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\nseries_info = train_csv[train_csv['SeriesInstanceUID'] == series_uid]\n\nif len(series_info) > 0:\n    print(f\"  Modality in CSV: {series_info['Modality'].values[0]}\")\n    print(f\"  Patient Age: {series_info['PatientAge'].values[0]}\")\n    print(f\"  Patient Sex: {series_info['PatientSex'].values[0]}\")\n    print(f\"  Aneurysm Present: {series_info['Aneurysm Present'].values[0]}\")\nelse:\n    print(\"  ⚠ Series not found in train.csv\")\n\n# ============================================\n# STEP 9: Save NIfTI with correct orientation\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 9: Save as NIfTI\")\nprint(\"=\"*60)\n\n# Get spacing\npixel_spacing = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n\nif len(slices) > 1:\n    try:\n        pos1 = np.array([float(x) for x in slices[0].ImagePositionPatient])\n        pos2 = np.array([float(x) for x in slices[1].ImagePositionPatient])\n        slice_spacing = abs(np.linalg.norm(pos2 - pos1))\n    except:\n        slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\nelse:\n    slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n\nprint(f\"  Pixel spacing: {pixel_spacing}\")\nprint(f\"  Slice spacing: {slice_spacing:.2f} mm\")\n\n# Transpose for NIfTI: (slices, H, W) -> (H, W, slices)\nvolume_nifti = np.transpose(volume_hu, (1, 2, 0))\nprint(f\"  NIfTI volume shape: {volume_nifti.shape}\")\n\n# Affine matrix\naffine = np.eye(4)\naffine[0, 0] = pixel_spacing[0]\naffine[1, 1] = pixel_spacing[1]\naffine[2, 2] = slice_spacing\n\n# Save\noutput_path = f'{series_uid[:20]}_debug.nii.gz'\nnifti_img = nib.Nifti1Image(volume_nifti.astype(np.int16), affine)\nnib.save(nifti_img, output_path)\nprint(f\"  ✓ Saved: {output_path}\")\n\n# ============================================\n# STEP 10: Reload and verify NIfTI\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"STEP 10: Verify saved NIfTI\")\nprint(\"=\"*60)\n\nimg = nib.load(output_path)\ndata = img.get_fdata()\n\nprint(f\"  Shape: {data.shape}\")\nprint(f\"  Dtype: {data.dtype}\")\nprint(f\"  Min: {data.min():.0f}\")\nprint(f\"  Max: {data.max():.0f}\")\nprint(f\"  Voxel size: {img.header.get_zooms()}\")\n\n# Display NIfTI slices\nmid_z = data.shape[2] // 2\nindices = [mid_z - 30, mid_z, mid_z + 30]\n\nfig, axes = plt.subplots(1, 3, figsize=(15, 5))\nfor ax, idx in zip(axes, indices):\n    idx = np.clip(idx, 0, data.shape[2]-1)\n    slice_data = data[:, :, idx]\n    # Auto contrast\n    ax.imshow(slice_data, cmap='gray', origin='lower',\n              vmin=np.percentile(slice_data, 1),\n              vmax=np.percentile(slice_data, 99))\n    ax.set_title(f'NIfTI Slice {idx} (auto contrast)')\n    ax.axis('off')\n\nplt.suptitle('Reloaded NIfTI - Auto Contrast')\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"DEBUG COMPLETE\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T11:59:48.739131Z","iopub.execute_input":"2026-01-05T11:59:48.741372Z","iopub.status.idle":"2026-01-05T12:00:01.944423Z","shell.execute_reply.started":"2026-01-05T11:59:48.741317Z","shell.execute_reply":"2026-01-05T12:00:01.943335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 1: Install packages\n# ============================================\n!pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel\n\n# ============================================\n# CELL 2: Import and filter CTA ONLY\n# ============================================\nimport pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\nimport os\n\n# Load train.csv\ntrain_csv = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\nprint(\"Modality distribution:\")\nprint(train_csv['Modality'].value_counts())\n\n# ============================================\n# FILTER CTA ONLY (NOT MRA!)\n# ============================================\ncta_only = train_csv[train_csv['Modality'] == 'CTA']\nmra_only = train_csv[train_csv['Modality'] == 'MRA']\n\nprint(f\"\\nCTA series: {len(cta_only)}\")\nprint(f\"MRA series: {len(mra_only)}\")\n\nif len(cta_only) == 0:\n    print(\"\\n⚠️ NO CTA SERIES FOUND! This dataset might only contain MRA.\")\n    print(\"Proceeding with MRA instead...\")\n    selected = mra_only.sample(n=5, random_state=42)\n    is_cta = False\nelse:\n    selected = cta_only.sample(n=5, random_state=42)\n    is_cta = True\n\nprint(f\"\\nSelected 5 {'CTA' if is_cta else 'MRA'} series:\")\nfor _, row in selected.iterrows():\n    status = \"ANEURYSM\" if row['Aneurysm Present'] == 1 else \"Normal\"\n    print(f\"  {row['SeriesInstanceUID'][:35]}... | {row['Modality']} | {row['PatientAge']}yo | {status}\")\n\n# ============================================\n# CELL 3: Convert DICOM to NIfTI (handles both CTA and MRA)\n# ============================================\ndef dicom_to_nifti(series_uid, input_base_dir, output_dir='nifti_output'):\n    \"\"\"Convert DICOM series to NIfTI, handling both CTA and MRA\"\"\"\n    \n    series_folder = Path(input_base_dir) / series_uid\n    dcm_files = sorted(series_folder.glob('*.dcm'))\n    \n    if len(dcm_files) == 0:\n        print(f\"  ⚠ No DICOM files found\")\n        return None\n    \n    print(f\"  Found {len(dcm_files)} DICOM files\")\n    \n    # Read all slices\n    slices = []\n    for f in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(f))\n            slices.append(ds)\n        except:\n            pass\n    \n    if len(slices) == 0:\n        return None\n    \n    # Sort by z-position\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except:\n        try:\n            slices.sort(key=lambda x: int(x.InstanceNumber))\n        except:\n            pass\n    \n    # Stack into volume\n    volume = np.stack([s.pixel_array for s in slices], axis=0)\n    \n    # Apply rescale for CT (CTA has RescaleSlope/Intercept, MRA doesn't)\n    first = slices[0]\n    modality = getattr(first, 'Modality', 'Unknown')\n    \n    if hasattr(first, 'RescaleSlope'):\n        slope = float(first.RescaleSlope)\n        intercept = float(getattr(first, 'RescaleIntercept', 0))\n        volume = volume.astype(np.float32) * slope + intercept\n        print(f\"  Applied rescale: slope={slope}, intercept={intercept}\")\n    else:\n        volume = volume.astype(np.float32)\n        print(f\"  No rescale (MRI data)\")\n    \n    print(f\"  Volume shape: {volume.shape}\")\n    print(f\"  Value range: [{volume.min():.0f}, {volume.max():.0f}]\")\n    \n    # Get spacing\n    pixel_spacing = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n    \n    if len(slices) > 1:\n        try:\n            pos1 = np.array([float(x) for x in slices[0].ImagePositionPatient])\n            pos2 = np.array([float(x) for x in slices[1].ImagePositionPatient])\n            slice_spacing = abs(np.linalg.norm(pos2 - pos1))\n        except:\n            slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    else:\n        slice_spacing = float(getattr(first, 'SliceThickness', 1.0))\n    \n    # Transpose for NIfTI: (slices, H, W) -> (H, W, slices)\n    volume = np.transpose(volume, (1, 2, 0))\n    \n    # Affine matrix\n    affine = np.eye(4)\n    affine[0, 0] = pixel_spacing[0]\n    affine[1, 1] = pixel_spacing[1]\n    affine[2, 2] = slice_spacing\n    \n    # Save\n    os.makedirs(output_dir, exist_ok=True)\n    output_path = Path(output_dir) / f\"{series_uid}.nii.gz\"\n    \n    nifti_img = nib.Nifti1Image(volume.astype(np.int16), affine)\n    nib.save(nifti_img, str(output_path))\n    \n    print(f\"  ✓ Saved: {output_path.name}\")\n    return output_path, modality\n\n# ============================================\n# CELL 4: Convert selected series\n# ============================================\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\nOUTPUT_DIR = 'nifti_output'\n\nseries_uids = selected['SeriesInstanceUID'].tolist()\n\nprint(f\"\\nConverting {len(series_uids)} series to NIfTI...\\n\")\n\nconverted = []\nfor i, uid in enumerate(series_uids, 1):\n    print(f\"[{i}/{len(series_uids)}] {uid[:40]}...\")\n    result = dicom_to_nifti(uid, INPUT_DIR, OUTPUT_DIR)\n    if result:\n        converted.append(result)\n    print()\n\nprint(f\"✓ Converted {len(converted)} series\")\n\n# ============================================\n# CELL 5: Visualize with CORRECT windowing\n# ============================================\ndef visualize_nifti_auto(nifti_path, modality='Unknown'):\n    \"\"\"Visualize NIfTI with auto contrast (works for both CTA and MRA)\"\"\"\n    \n    img = nib.load(str(nifti_path))\n    data = img.get_fdata()\n    \n    print(f\"\\nNIfTI: {Path(nifti_path).name}\")\n    print(f\"  Shape: {data.shape}\")\n    print(f\"  Value range: [{data.min():.0f}, {data.max():.0f}]\")\n    \n    # Select slices\n    n_slices = 9\n    indices = np.linspace(0, data.shape[2]-1, n_slices, dtype=int)\n    \n    # Use percentile-based windowing (works for any modality)\n    vmin = np.percentile(data, 1)\n    vmax = np.percentile(data, 99)\n    \n    fig, axes = plt.subplots(3, 3, figsize=(12, 12))\n    for ax, idx in zip(axes.flat, indices):\n        ax.imshow(data[:, :, idx], cmap='gray', origin='lower', vmin=vmin, vmax=vmax)\n        ax.set_title(f'Slice {idx}')\n        ax.axis('off')\n    \n    plt.suptitle(f'{modality}: {Path(nifti_path).stem[:30]}... (Auto Contrast)', fontsize=12)\n    plt.tight_layout()\n    plt.show()\n\n# Visualize first converted file\nif converted:\n    nifti_path, modality = converted[0]\n    visualize_nifti_auto(nifti_path, modality)\n\n# ============================================\n# CELL 6: Create ZIP for download\n# ============================================\nimport shutil\n\nzip_path = shutil.make_archive('nifti_5_samples', 'zip', OUTPUT_DIR)\nprint(f\"\\n✓ Created: {zip_path}\")\nprint(f\"  Download from Output panel →\")\n\n# List files\nprint(\"\\nNIfTI files:\")\nfor f in Path(OUTPUT_DIR).glob('*.nii.gz'):\n    size_mb = f.stat().st_size / (1024 * 1024)\n    print(f\"  {f.name[:40]}... ({size_mb:.1f} MB)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T12:36:29.905482Z","iopub.execute_input":"2026-01-05T12:36:29.905947Z","iopub.status.idle":"2026-01-05T12:39:16.669611Z","shell.execute_reply.started":"2026-01-05T12:36:29.905911Z","shell.execute_reply":"2026-01-05T12:39:16.668172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Check files are in correct location\nimport os\nfor f in os.listdir('/kaggle/working/'):\n    print(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T12:55:56.926861Z","iopub.execute_input":"2026-01-05T12:55:56.928153Z","iopub.status.idle":"2026-01-05T12:55:56.940588Z","shell.execute_reply.started":"2026-01-05T12:55:56.928081Z","shell.execute_reply":"2026-01-05T12:55:56.939006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# COMPLETE WORKING CODE: DICOM to NIfTI + Download ZIP\n# ============================================\n\n# CELL 1: Install\n!pip install pydicom matplotlib pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel -q\n\n# CELL 2: Import\nimport pydicom\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\nimport os\nimport shutil\n\n# ============================================\n# CELL 3: Select 5 series (CTA or MRA)\n# ============================================\ntrain_csv = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\nprint(\"Modality distribution:\")\nprint(train_csv['Modality'].value_counts())\n\n# Try CTA first, fallback to MRA\ncta_df = train_csv[train_csv['Modality'] == 'CTA']\nif len(cta_df) > 0:\n    selected = cta_df.sample(n=5, random_state=42)\n    print(f\"\\nSelected 5 CTA series\")\nelse:\n    selected = train_csv.sample(n=5, random_state=42)\n    print(f\"\\nSelected 5 series (MRA)\")\n\nprint(selected[['SeriesInstanceUID', 'Modality', 'PatientAge', 'Aneurysm Present']].to_string())\n\n# ============================================\n# CELL 4: Convert DICOM to NIfTI\n# ============================================\ndef dicom_to_nifti(series_uid, input_dir, output_dir):\n    \"\"\"Convert DICOM series to NIfTI\"\"\"\n    series_folder = Path(input_dir) / series_uid\n    dcm_files = sorted(series_folder.glob('*.dcm'))\n    \n    if len(dcm_files) == 0:\n        print(f\"  ⚠ No files found\")\n        return None\n    \n    # Read slices\n    slices = [pydicom.dcmread(str(f)) for f in dcm_files]\n    \n    # Sort by z-position\n    try:\n        slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    except:\n        slices.sort(key=lambda x: int(getattr(x, 'InstanceNumber', 0)))\n    \n    # Stack volume\n    volume = np.stack([s.pixel_array for s in slices], axis=0).astype(np.float32)\n    \n    # Apply rescale if available (CT only)\n    first = slices[0]\n    if hasattr(first, 'RescaleSlope'):\n        volume = volume * float(first.RescaleSlope) + float(getattr(first, 'RescaleIntercept', 0))\n    \n    # Get spacing\n    ps = [float(x) for x in getattr(first, 'PixelSpacing', [1, 1])]\n    if len(slices) > 1:\n        try:\n            z1 = float(slices[0].ImagePositionPatient[2])\n            z2 = float(slices[1].ImagePositionPatient[2])\n            ss = abs(z2 - z1)\n        except:\n            ss = float(getattr(first, 'SliceThickness', 1))\n    else:\n        ss = 1.0\n    \n    # Transpose: (slices, H, W) -> (H, W, slices)\n    volume = np.transpose(volume, (1, 2, 0))\n    \n    # Affine\n    affine = np.diag([ps[0], ps[1], ss, 1])\n    \n    # Save\n    os.makedirs(output_dir, exist_ok=True)\n    out_path = Path(output_dir) / f\"{series_uid}.nii.gz\"\n    nib.save(nib.Nifti1Image(volume.astype(np.int16), affine), str(out_path))\n    \n    print(f\"  ✓ {out_path.name} | Shape: {volume.shape}\")\n    return out_path\n\n# Convert all selected series\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\nOUTPUT_DIR = '/kaggle/working/nifti_output'  # MUST be in /kaggle/working/\n\nprint(f\"\\nConverting to NIfTI...\\n\")\nfor i, uid in enumerate(selected['SeriesInstanceUID'], 1):\n    print(f\"[{i}/5] {uid[:40]}...\")\n    dicom_to_nifti(uid, INPUT_DIR, OUTPUT_DIR)\n\n# ============================================\n# CELL 5: Create ZIP in /kaggle/working/\n# ============================================\n# ZIP file MUST be in /kaggle/working/ to be downloadable\nZIP_PATH = '/kaggle/working/nifti_5_samples.zip'\n\nshutil.make_archive(\n    '/kaggle/working/nifti_5_samples',  # Name (without .zip)\n    'zip',                               # Format\n    OUTPUT_DIR                           # Source folder\n)\n\nprint(f\"\\n✓ Created: {ZIP_PATH}\")\nprint(f\"  Size: {os.path.getsize(ZIP_PATH) / (1024*1024):.1f} MB\")\n\n# ============================================\n# CELL 6: Create Download Link\n# ============================================\nfrom IPython.display import FileLink, display\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"📥 DOWNLOAD YOUR ZIP FILE:\")\nprint(\"=\"*60)\n\n# Method 1: Clickable link (works in Kaggle)\ndisplay(FileLink(ZIP_PATH, result_html_prefix=\"Click to download: \"))\n\n# Method 2: Instructions\nprint(\"\"\"\nALTERNATIVE DOWNLOAD METHODS:\n\n1. CLICK THE LINK ABOVE ☝️\n\n2. OR use the Output panel:\n   → Look at the RIGHT sidebar\n   → Click \"Output\" tab\n   → Find \"nifti_5_samples.zip\"\n   → Click to download\n\n3. OR after \"Save & Run All\":\n   → Go to your notebook page\n   → Click \"Output\" tab at the top\n   → Download from there\n\"\"\")\n\n# List the files in working directory\nprint(\"\\nFiles in /kaggle/working/:\")\nfor f in Path('/kaggle/working/').glob('*'):\n    if f.is_file():\n        size = f.stat().st_size / (1024*1024)\n        print(f\"  {f.name} ({size:.1f} MB)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T12:57:40.395215Z","iopub.execute_input":"2026-01-05T12:57:40.395757Z","iopub.status.idle":"2026-01-05T13:00:12.284155Z","shell.execute_reply.started":"2026-01-05T12:57:40.395717Z","shell.execute_reply":"2026-01-05T13:00:12.282397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CELL 1: Install packages\n# ============================================\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel requests -q\n\n# ============================================\n# CELL 2: Convert DICOM to NIfTI + Upload to file.io\n# ============================================\nimport pydicom\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\nimport os\nimport shutil\nimport requests\n\n# ----- CONFIGURATION -----\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\nOUTPUT_DIR = '/kaggle/working/nifti_output'\nZIP_PATH = '/kaggle/working/nifti_5_samples.zip'\nNUM_SAMPLES = 5\n\n# ----- STEP 1: Select series from CSV -----\nprint(\"STEP 1: Selecting series...\")\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\nselected = df.sample(n=NUM_SAMPLES, random_state=42)\nprint(f\"  Selected {NUM_SAMPLES} series\\n\")\n\n# ----- STEP 2: Convert DICOM to NIfTI -----\nprint(\"STEP 2: Converting DICOM to NIfTI...\")\nos.makedirs(OUTPUT_DIR, exist_ok=True)\n\nconverted_count = 0\n\nfor i, uid in enumerate(selected['SeriesInstanceUID'], 1):\n    print(f\"  [{i}/{NUM_SAMPLES}] {uid[:30]}...\")\n    \n    try:\n        # Read DICOM files\n        series_path = Path(INPUT_DIR, uid)\n        dcm_files = sorted(series_path.glob('*.dcm'))\n        \n        if len(dcm_files) == 0:\n            print(f\"    ⚠ No DICOM files found, skipping\")\n            continue\n        \n        print(f\"    Found {len(dcm_files)} DICOM files\")\n        \n        # Read all slices\n        slices = []\n        for f in dcm_files:\n            try:\n                ds = pydicom.dcmread(str(f))\n                slices.append(ds)\n            except Exception as e:\n                pass\n        \n        if len(slices) < 2:\n            print(f\"    ⚠ Only {len(slices)} slice(s), skipping\")\n            continue\n        \n        # Sort by z-position\n        try:\n            slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n        except:\n            try:\n                slices.sort(key=lambda x: int(x.InstanceNumber))\n            except:\n                pass\n        \n        # Stack into volume\n        volume = np.stack([s.pixel_array for s in slices], axis=0).astype(np.float32)\n        print(f\"    Volume shape: {volume.shape}\")\n        \n        # Check dimensions\n        if volume.ndim != 3:\n            print(f\"    ⚠ Unexpected dimensions: {volume.ndim}D, skipping\")\n            continue\n        \n        # Apply rescale if available\n        first = slices[0]\n        if hasattr(first, 'RescaleSlope'):\n            slope = float(first.RescaleSlope)\n            intercept = float(getattr(first, 'RescaleIntercept', 0))\n            volume = volume * slope + intercept\n        \n        # Get spacing\n        ps = [float(x) for x in getattr(first, 'PixelSpacing', [1.0, 1.0])]\n        \n        if len(slices) > 1:\n            try:\n                z1 = float(slices[0].ImagePositionPatient[2])\n                z2 = float(slices[1].ImagePositionPatient[2])\n                ss = abs(z2 - z1)\n                if ss == 0:\n                    ss = float(getattr(first, 'SliceThickness', 1.0))\n            except:\n                ss = float(getattr(first, 'SliceThickness', 1.0))\n        else:\n            ss = float(getattr(first, 'SliceThickness', 1.0))\n        \n        # Transpose: (slices, H, W) -> (H, W, slices)\n        volume = np.transpose(volume, (1, 2, 0))\n        \n        # Create affine matrix\n        affine = np.diag([ps[0], ps[1], ss, 1.0])\n        \n        # Save NIfTI\n        output_path = f\"{OUTPUT_DIR}/{uid}.nii.gz\"\n        nib.save(nib.Nifti1Image(volume.astype(np.int16), affine), output_path)\n        \n        converted_count += 1\n        print(f\"    ✓ Saved: {volume.shape}\")\n        \n    except Exception as e:\n        print(f\"    ❌ Error: {e}\")\n        continue\n\nprint(f\"\\n  ✓ Converted {converted_count}/{NUM_SAMPLES} series\\n\")\n\n# ----- STEP 3: Create ZIP -----\nprint(\"STEP 3: Creating ZIP file...\")\n\nif converted_count == 0:\n    print(\"  ❌ No files converted! Cannot create ZIP.\")\nelse:\n    shutil.make_archive('/kaggle/working/nifti_5_samples', 'zip', OUTPUT_DIR)\n    zip_size = os.path.getsize(ZIP_PATH) / (1024 * 1024)\n    print(f\"  ✓ ZIP created: {zip_size:.1f} MB\\n\")\n\n    # ----- STEP 4: Upload to file.io -----\n    print(\"STEP 4: Uploading to file.io...\")\n    print(\"  Please wait, this may take a few minutes...\\n\")\n\n    with open(ZIP_PATH, 'rb') as f:\n        response = requests.post(\n            'https://file.io',\n            files={'file': ('nifti_5_samples.zip', f)}\n        )\n\n    # ----- STEP 5: Get download link -----\n    if response.status_code == 200:\n        result = response.json()\n        if result.get('success'):\n            print(\"=\" * 60)\n            print(\"✅ UPLOAD SUCCESSFUL!\")\n            print(\"=\" * 60)\n            print(\"\")\n            print(\"📥 YOUR DOWNLOAD LINK:\")\n            print(\"\")\n            print(f\"   {result['link']}\")\n            print(\"\")\n            print(\"=\" * 60)\n            print(\"⚠️  IMPORTANT: This link works for 1 download only!\")\n            print(\"⚠️  Copy the link and open in your browser now!\")\n            print(\"=\" * 60)\n        else:\n            print(f\"❌ Upload failed: {result}\")\n    else:\n        print(f\"❌ Upload failed with status: {response.status_code}\")\n        print(f\"   Response: {response.text}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T14:33:47.608276Z","iopub.execute_input":"2026-01-05T14:33:47.608629Z","iopub.status.idle":"2026-01-05T14:34:19.581344Z","shell.execute_reply.started":"2026-01-05T14:33:47.608587Z","shell.execute_reply":"2026-01-05T14:34:19.579721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# CONVERT 1 DICOM SERIES TO NIFTI + DOWNLOAD\n# ============================================\n\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel requests -q\n\nimport pydicom\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\nimport requests\n\n# ============================================\n# STEP 1: Pick one series\n# ============================================\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\nseries_uid = df.sample(n=1, random_state=42)['SeriesInstanceUID'].values[0]\n\nprint(f\"Selected series: {series_uid[:40]}...\")\n\n# ============================================\n# STEP 2: Convert to NIfTI\n# ============================================\nprint(\"\\nConverting to NIfTI...\")\n\ndcm_files = sorted(Path(INPUT_DIR, series_uid).glob('*.dcm'))\nprint(f\"  Found {len(dcm_files)} DICOM files\")\n\nslices = [pydicom.dcmread(str(f)) for f in dcm_files]\n\n# Sort by z-position\ntry:\n    slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\nexcept:\n    pass\n\n# Stack\nvolume = np.stack([s.pixel_array for s in slices], axis=0).astype(np.float32)\nprint(f\"  Volume shape: {volume.shape}\")\n\n# Rescale if CT\nfirst = slices[0]\nif hasattr(first, 'RescaleSlope'):\n    volume = volume * float(first.RescaleSlope) + float(getattr(first, 'RescaleIntercept', 0))\n\n# Spacing\nps = [float(x) for x in getattr(first, 'PixelSpacing', [1, 1])]\ntry:\n    ss = abs(float(slices[1].ImagePositionPatient[2]) - float(slices[0].ImagePositionPatient[2]))\nexcept:\n    ss = float(getattr(first, 'SliceThickness', 1))\n\n# Transpose and save\nvolume = np.transpose(volume, (1, 2, 0))\naffine = np.diag([ps[0], ps[1], ss, 1])\n\noutput_path = '/kaggle/working/single_volume.nii.gz'\nnib.save(nib.Nifti1Image(volume.astype(np.int16), affine), output_path)\n\nimport os\nfile_size = os.path.getsize(output_path) / (1024 * 1024)\nprint(f\"  ✓ Saved: {file_size:.1f} MB\")\n\n# ============================================\n# STEP 3: Upload to file.io\n# ============================================\nprint(f\"\\nUploading to file.io...\")\n\nwith open(output_path, 'rb') as f:\n    response = requests.post('https://file.io', files={'file': ('single_volume.nii.gz', f)})\n\nif response.status_code == 200:\n    try:\n        result = response.json()\n        if result.get('success'):\n            print(\"\\n\" + \"=\" * 50)\n            print(\"✅ SUCCESS!\")\n            print(\"=\" * 50)\n            print(f\"\\n📥 DOWNLOAD LINK:\\n\")\n            print(f\"   {result['link']}\")\n            print(f\"\\n⚠️ Link works for 1 download only!\")\n            print(\"=\" * 50)\n        else:\n            print(f\"Upload failed: {result}\")\n    except:\n        print(f\"Response: {response.text[:200]}\")\n        print(\"\\nfile.io failed. Trying transfer.sh...\")\n        \n        # Backup: transfer.sh\n        import subprocess\n        result = subprocess.run(\n            ['curl', '--upload-file', output_path, 'https://transfer.sh/single_volume.nii.gz'],\n            capture_output=True, text=True\n        )\n        if result.stdout.startswith('http'):\n            print(\"\\n\" + \"=\" * 50)\n            print(\"✅ SUCCESS!\")\n            print(\"=\" * 50)\n            print(f\"\\n📥 DOWNLOAD LINK:\\n\")\n            print(f\"   {result.stdout.strip()}\")\n            print(\"=\" * 50)\nelse:\n    print(f\"Failed: {response.status_code}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T14:41:57.438167Z","iopub.execute_input":"2026-01-05T14:41:57.439577Z","iopub.status.idle":"2026-01-05T14:42:04.765379Z","shell.execute_reply.started":"2026-01-05T14:41:57.439518Z","shell.execute_reply":"2026-01-05T14:42:04.764283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# SIMPLE: Upload existing NIfTI to transfer.sh\n# ============================================\n\n# Just run this ONE line:\n!curl --upload-file /kaggle/working/single_volume.nii.gz https://transfer.sh/single_volume.nii.gz","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 1: Install\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg nibabel -q\n\n# CELL 2: Filter CTA and convert first one\nimport pydicom\nimport numpy as np\nimport pandas as pd\nimport nibabel as nib\nfrom pathlib import Path\n\n# Load CSV\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\n# Filter CTA only\ncta_df = df[df['Modality'] == 'CTA']\nprint(f\"Total CTA series: {len(cta_df)}\")\n\n# Check if CTA exists\nif len(cta_df) == 0:\n    print(\"No CTA found. Showing available modalities:\")\n    print(df['Modality'].value_counts())\nelse:\n    # Get first CTA series\n    first_cta = cta_df.iloc[0]\n    series_uid = first_cta['SeriesInstanceUID']\n    print(f\"First CTA: {series_uid}\")\n    \n    # Read DICOM\n    INPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n    dcm_files = sorted(Path(INPUT_DIR, series_uid).glob('*.dcm'))\n    print(f\"DICOM files: {len(dcm_files)}\")\n    \n    # Load slices\n    slices = [pydicom.dcmread(str(f)) for f in dcm_files]\n    slices.sort(key=lambda x: float(x.ImagePositionPatient[2]))\n    \n    # Stack volume\n    volume = np.stack([s.pixel_array for s in slices], axis=0).astype(np.float32)\n    \n    # Rescale to HU\n    first = slices[0]\n    if hasattr(first, 'RescaleSlope'):\n        volume = volume * float(first.RescaleSlope) + float(getattr(first, 'RescaleIntercept', 0))\n    \n    # Spacing\n    ps = [float(x) for x in first.PixelSpacing]\n    ss = abs(float(slices[1].ImagePositionPatient[2]) - float(slices[0].ImagePositionPatient[2]))\n    \n    # Save NIfTI\n    volume = np.transpose(volume, (1, 2, 0))\n    affine = np.diag([ps[0], ps[1], ss, 1])\n    nib.save(nib.Nifti1Image(volume.astype(np.int16), affine), '/kaggle/working/cta.nii.gz')\n    \n    import os\n    size = os.path.getsize('/kaggle/working/cta.nii.gz') / (1024*1024)\n    print(f\"Saved: /kaggle/working/cta.nii.gz ({size:.1f} MB)\")\n\n# CELL 3: Download\n!curl --upload-file /kaggle/working/cta.nii.gz https://transfer.sh/cta.nii.gz","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-05T15:12:01.038552Z","iopub.execute_input":"2026-01-05T15:12:01.039666Z","iopub.status.idle":"2026-01-05T15:12:19.996652Z","shell.execute_reply.started":"2026-01-05T15:12:01.039627Z","shell.execute_reply":"2026-01-05T15:12:19.995583Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# CELL 1: Install\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg -q\n\n# CELL 2: Filter CTA and extract metadata\nimport pydicom\nimport pandas as pd\nfrom pathlib import Path\n\n# Load train.csv\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\n# Filter CTA only\ncta_df = df[df['Modality'] == 'CTA'].copy()\nprint(f\"Total CTA series: {len(cta_df)}\")\n\n# Path to series folder\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n\n# List to store metadata\nmetadata_list = []\n\n# Read metadata from each CTA series\nfor i, row in cta_df.iterrows():\n    series_uid = row['SeriesInstanceUID']\n    \n    # Get first DICOM file from series\n    series_path = Path(INPUT_DIR, series_uid)\n    dcm_files = list(series_path.glob('*.dcm'))\n    \n    if len(dcm_files) == 0:\n        continue\n    \n    # Read first DICOM file for metadata\n    ds = pydicom.dcmread(str(dcm_files[0]))\n    \n    # Extract metadata\n    meta = {\n        'SeriesInstanceUID': series_uid,\n        'PatientAge': row['PatientAge'],\n        'PatientSex': row['PatientSex'],\n        'AneurysmPresent': row['Aneurysm Present'],\n        'Modality': getattr(ds, 'Modality', 'N/A'),\n        'Manufacturer': getattr(ds, 'Manufacturer', 'N/A'),\n        'ManufacturerModelName': getattr(ds, 'ManufacturerModelName', 'N/A'),\n        'SliceThickness': getattr(ds, 'SliceThickness', 'N/A'),\n        'KVP': getattr(ds, 'KVP', 'N/A'),\n        'Rows': getattr(ds, 'Rows', 'N/A'),\n        'Columns': getattr(ds, 'Columns', 'N/A'),\n        'PixelSpacing': str(getattr(ds, 'PixelSpacing', 'N/A')),\n        'RescaleSlope': getattr(ds, 'RescaleSlope', 'N/A'),\n        'RescaleIntercept': getattr(ds, 'RescaleIntercept', 'N/A'),\n        'WindowCenter': str(getattr(ds, 'WindowCenter', 'N/A')),\n        'WindowWidth': str(getattr(ds, 'WindowWidth', 'N/A')),\n        'ConvolutionKernel': str(getattr(ds, 'ConvolutionKernel', 'N/A')),\n        'NumberOfSlices': len(dcm_files),\n    }\n    \n    metadata_list.append(meta)\n    \n    # Print progress\n    if len(metadata_list) % 50 == 0:\n        print(f\"Processed {len(metadata_list)} series...\")\n\n# Create DataFrame\nmetadata_df = pd.DataFrame(metadata_list)\n\n# Save to CSV\noutput_path = '/kaggle/working/cta_metadata.csv'\nmetadata_df.to_csv(output_path, index=False)\n\nprint(f\"\\n✓ Saved {len(metadata_df)} CTA series metadata to: {output_path}\")\nprint(f\"\\nColumns: {list(metadata_df.columns)}\")\nprint(f\"\\nFirst 5 rows:\")\nprint(metadata_df.head())\n\n# CELL 3: Download CSV\n!curl --upload-file /kaggle/working/cta_metadata.csv https://transfer.sh/cta_metadata.csv","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Test Run","metadata":{}},{"cell_type":"code","source":"# CELL 1: Install\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg -q\n\n# CELL 2: Extract metadata for EVERY DICOM file (TEST: 5 series only)\nimport pydicom\nimport pandas as pd\nfrom pathlib import Path\n\n# Load and filter CTA\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\ncta_df = df[df['Modality'] == 'CTA']\nprint(f\"Total CTA series: {len(cta_df)}\")\n\n# TEST: Select only 5 CTA series\ncta_df = cta_df.head(5)\nprint(f\"Testing with: {len(cta_df)} series\\n\")\n\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n\n# Extract metadata for each DICOM file\nall_metadata = []\n\nfor series_idx, (_, row) in enumerate(cta_df.iterrows()):\n    series_uid = row['SeriesInstanceUID']\n    dcm_files = list(Path(INPUT_DIR, series_uid).glob('*.dcm'))\n    \n    print(f\"[{series_idx+1}/5] Series: {series_uid[:40]}...\")\n    print(f\"      DICOM files: {len(dcm_files)}\")\n    \n    if len(dcm_files) == 0:\n        continue\n    \n    # Read EACH DICOM file in series\n    for dcm_file in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(dcm_file))\n            \n            # Start with CSV columns\n            meta = row.to_dict()\n            meta['DICOMFileName'] = dcm_file.name\n            meta['TotalSlicesInSeries'] = len(dcm_files)\n            \n            # Add ALL DICOM fields\n            for elem in ds:\n                if elem.tag == (0x7fe0, 0x0010):  # Skip pixel data\n                    continue\n                \n                tag_name = elem.keyword if elem.keyword else f\"Tag_{elem.tag}\"\n                \n                try:\n                    value = elem.value\n                    if isinstance(value, bytes):\n                        value = 'bytes'\n                    elif isinstance(value, pydicom.sequence.Sequence):\n                        value = f'Sequence({len(value)})'\n                    else:\n                        value = str(value)\n                except:\n                    value = None\n                \n                meta[tag_name] = value\n            \n            all_metadata.append(meta)\n            \n        except Exception as e:\n            print(f\"      Error: {e}\")\n            continue\n    \n    print(f\"      ✓ Processed\")\n\n# Save to CSV\nmetadata_df = pd.DataFrame(all_metadata)\nmetadata_df.to_csv('/kaggle/working/test_cta_metadata.csv', index=False)\n\nprint(f\"\\n\" + \"=\"*50)\nprint(f\"✓ Saved: test_cta_metadata.csv\")\nprint(f\"  Total DICOM files (rows): {len(metadata_df)}\")\nprint(f\"  Total columns: {len(metadata_df.columns)}\")\nprint(f\"=\"*50)\n\n# Show sample\nprint(f\"\\nFirst 5 columns:\")\nprint(metadata_df.columns[:5].tolist())\n\nprint(f\"\\nLast 5 columns:\")\nprint(metadata_df.columns[-5:].tolist())\n\nprint(f\"\\nSample data (first 3 rows, key columns):\")\nkey_cols = ['SeriesInstanceUID', 'DICOMFileName', 'SOPInstanceUID', 'InstanceNumber', 'SliceLocation']\nexisting_cols = [c for c in key_cols if c in metadata_df.columns]\nprint(metadata_df[existing_cols].head(3))\n\n# CELL 3: Download\n!curl --upload-file /kaggle/working/test_cta_metadata.csv https://transfer.sh/test_cta_metadata.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-06T16:54:17.057153Z","iopub.execute_input":"2026-01-06T16:54:17.057806Z","iopub.status.idle":"2026-01-06T16:54:57.744431Z","shell.execute_reply.started":"2026-01-06T16:54:17.057756Z","shell.execute_reply":"2026-01-06T16:54:57.74323Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# One row per series","metadata":{}},{"cell_type":"code","source":"# CELL 1: Install\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg -q\n\n# CELL 2: Extract metadata - ONE ROW PER SERIES\nimport pydicom\nimport pandas as pd\nfrom pathlib import Path\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Load and filter CTA\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\ncta_df = df[df['Modality'] == 'CTA']\nprint(f\"Total CTA series: {len(cta_df)}\")\n\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n\n# Extract metadata - ONE ROW PER SERIES\nall_metadata = []\n\nfor series_idx, (_, row) in enumerate(cta_df.iterrows()):\n    series_uid = row['SeriesInstanceUID']\n    dcm_files = list(Path(INPUT_DIR, series_uid).glob('*.dcm'))\n    \n    if len(dcm_files) == 0:\n        continue\n    \n    # Read FIRST DICOM file only (metadata same for all slices)\n    try:\n        ds = pydicom.dcmread(str(dcm_files[0]))\n        \n        # Start with CSV columns\n        meta = row.to_dict()\n        meta['NumberOfSlices'] = len(dcm_files)\n        \n        # Add ALL DICOM fields\n        for elem in ds:\n            if elem.tag == (0x7fe0, 0x0010):  # Skip pixel data\n                continue\n            \n            tag_name = elem.keyword if elem.keyword else f\"Tag_{elem.tag}\"\n            \n            try:\n                value = elem.value\n                if isinstance(value, bytes):\n                    value = 'bytes'\n                elif isinstance(value, pydicom.sequence.Sequence):\n                    value = f'Sequence({len(value)})'\n                else:\n                    value = str(value)\n            except:\n                value = None\n            \n            meta[tag_name] = value\n        \n        all_metadata.append(meta)\n        \n    except:\n        continue\n    \n    # Progress every 100 series\n    if (series_idx + 1) % 100 == 0:\n        print(f\"Processed {series_idx + 1}/{len(cta_df)} series...\")\n\n# Save to CSV\nmetadata_df = pd.DataFrame(all_metadata)\nmetadata_df.to_csv('/kaggle/working/cta_metadata_all.csv', index=False)\n\nprint(f\"\\n\" + \"=\"*50)\nprint(f\"✓ COMPLETE!\")\nprint(f\"  Total series (rows): {len(metadata_df)}\")\nprint(f\"  Total columns: {len(metadata_df.columns)}\")\nprint(f\"  File: /kaggle/working/cta_metadata_all.csv\")\nprint(f\"=\"*50)\n\n# CELL 3: Download\nfrom IPython.display import FileLink\nimport os\n\nsize_mb = os.path.getsize('/kaggle/working/cta_metadata_all.csv') / (1024*1024)\nprint(f\"\\nFile size: {size_mb:.1f} MB\")\nprint(\"\\n📥 Click to download:\")\ndisplay(FileLink('/kaggle/working/cta_metadata_all.csv'))\nprint(\"\\nOR: Save Version → Output tab → Download\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T05:36:19.537211Z","iopub.execute_input":"2026-01-07T05:36:19.53748Z","iopub.status.idle":"2026-01-07T05:39:59.511597Z","shell.execute_reply.started":"2026-01-07T05:36:19.537452Z","shell.execute_reply":"2026-01-07T05:39:59.51031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CELL 1: Install\n!pip install pydicom pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg -q\n\n# CELL 2: Extract metadata for EVERY DICOM file (every SOPInstanceUID)\nimport pydicom\nimport pandas as pd\nfrom pathlib import Path\nimport warnings\nimport time\nwarnings.filterwarnings('ignore')\n\n# Load and filter CTA\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\ncta_df = df[df['Modality'] == 'CTA']\nprint(f\"Total CTA series: {len(cta_df)}\")\n\nINPUT_DIR = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\n\n# Extract metadata for EVERY DICOM file\nall_metadata = []\nstart_time = time.time()\n\nfor series_idx, (_, row) in enumerate(cta_df.iterrows()):\n    series_uid = row['SeriesInstanceUID']\n    dcm_files = list(Path(INPUT_DIR, series_uid).glob('*.dcm'))\n    \n    if len(dcm_files) == 0:\n        continue\n    \n    # Read EVERY DICOM file in series\n    for dcm_file in dcm_files:\n        try:\n            ds = pydicom.dcmread(str(dcm_file))\n            \n            # Start with CSV columns\n            meta = row.to_dict()\n            meta['DICOMFileName'] = dcm_file.name\n            meta['NumberOfSlicesInSeries'] = len(dcm_files)\n            \n            # Add ALL DICOM fields\n            for elem in ds:\n                if elem.tag == (0x7fe0, 0x0010):  # Skip pixel data\n                    continue\n                \n                tag_name = elem.keyword if elem.keyword else f\"Tag_{elem.tag}\"\n                \n                try:\n                    value = elem.value\n                    if isinstance(value, bytes):\n                        value = 'bytes'\n                    elif isinstance(value, pydicom.sequence.Sequence):\n                        value = f'Sequence({len(value)})'\n                    else:\n                        value = str(value)\n                except:\n                    value = None\n                \n                meta[tag_name] = value\n            \n            all_metadata.append(meta)\n            \n        except:\n            continue\n    \n    # Progress every 50 series\n    if (series_idx + 1) % 50 == 0:\n        elapsed = time.time() - start_time\n        rate = (series_idx + 1) / elapsed\n        remaining = (len(cta_df) - series_idx - 1) / rate / 60\n        print(f\"Processed {series_idx + 1}/{len(cta_df)} series | {len(all_metadata)} DICOM files | ~{remaining:.1f} min remaining\")\n\n# Save to CSV\nprint(\"\\nSaving to CSV...\")\nmetadata_df = pd.DataFrame(all_metadata)\nmetadata_df.to_csv('/kaggle/working/cta_all_sop_metadata.csv', index=False)\n\nelapsed_total = (time.time() - start_time) / 60\nprint(f\"\\n\" + \"=\"*60)\nprint(f\"✓ COMPLETE!\")\nprint(f\"  Total series: {cta_df.shape[0]}\")\nprint(f\"  Total DICOM files (rows): {len(metadata_df)}\")\nprint(f\"  Total columns: {len(metadata_df.columns)}\")\nprint(f\"  Time: {elapsed_total:.1f} minutes\")\nprint(f\"  File: /kaggle/working/cta_all_sop_metadata.csv\")\nprint(f\"=\"*60)\n\n# CELL 3: Download\nfrom IPython.display import FileLink\nimport os\n\nsize_mb = os.path.getsize('/kaggle/working/cta_all_sop_metadata.csv') / (1024*1024)\nprint(f\"\\nFile size: {size_mb:.1f} MB\")\nprint(\"\\n📥 Click to download:\")\ndisplay(FileLink('/kaggle/working/cta_all_sop_metadata.csv'))\nprint(\"\\nOR: Save Version → Output tab → Download\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:00:29.835497Z","iopub.execute_input":"2026-01-07T10:00:29.835837Z","iopub.status.idle":"2026-01-07T12:43:53.86264Z","shell.execute_reply.started":"2026-01-07T10:00:29.83581Z","shell.execute_reply":"2026-01-07T12:43:53.857065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Done')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T12:43:53.877645Z","iopub.execute_input":"2026-01-07T12:43:53.878047Z","iopub.status.idle":"2026-01-07T12:43:53.88354Z","shell.execute_reply.started":"2026-01-07T12:43:53.878013Z","shell.execute_reply":"2026-01-07T12:43:53.882667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}