{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":99552,"databundleVersionId":13851420,"isSourceIdPinned":false},{"sourceType":"datasetVersion","sourceId":13382569,"datasetId":8491061,"databundleVersionId":14092251},{"sourceType":"datasetVersion","sourceId":14048010,"datasetId":8943298,"databundleVersionId":14827121},{"sourceType":"datasetVersion","sourceId":14045202,"datasetId":8941532,"databundleVersionId":14824024},{"sourceType":"datasetVersion","sourceId":15727526,"datasetId":10074332,"databundleVersionId":16668720},{"sourceType":"datasetVersion","sourceId":15727550,"datasetId":10076883,"databundleVersionId":16668745},{"sourceType":"datasetVersion","sourceId":12780021,"datasetId":8079690,"databundleVersionId":13404554},{"sourceType":"modelInstanceVersion","sourceId":828450,"databundleVersionId":16625315,"modelInstanceId":629989,"modelId":641898,"isSourceIdPinned":false}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install huggingface_hub -q","metadata":{"_uuid":"b2fc5b66-a86c-4c9f-91e6-6deaf868af87","_cell_guid":"1acaaad1-6a30-46f7-af49-0d123d38df47","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T17:43:00.742285Z","iopub.execute_input":"2026-04-14T17:43:00.742893Z","iopub.status.idle":"2026-04-14T17:43:03.498907Z","shell.execute_reply.started":"2026-04-14T17:43:00.742863Z","shell.execute_reply":"2026-04-14T17:43:03.498071Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from huggingface_hub import HfApi, login\n\nlogin(token=\"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\")\napi = HfApi()\n\nREPO = \"diyasilawat/aneurysm-models\"\napi.create_repo(REPO, repo_type=\"model\", exist_ok=True)\nprint(f\"Repo ready ✅\")\n\n# Upload 5x 2D fold models\nfor i in range(5):\n    src = f\"/kaggle/input/rsna-aneurysm-5fold-models/models/tf_efficientnetv2_s.in21k_ft_in1k_fold{i}_best.pth\"\n    api.upload_file(path_or_fileobj=src, path_in_repo=f\"fold{i}_2d.pth\",\n                    repo_id=REPO, repo_type=\"model\")\n    print(f\"2D fold {i} uploaded ✅\")\n\n# Upload 3D fold models (v1 = folds 1,2,3 | v2 = folds 4,5)\nv1_path = \"/kaggle/input/datasets/diyasilawat/rsna-aneurysm-models-v1\"\nv2_path = \"/kaggle/input/datasets/diyasilawat/rsna-aneurysm-models-v2\"\n\nfor fold in [1, 2, 3]:\n    src = f\"{v1_path}/aneurysm_fold{fold}.pth\"\n    api.upload_file(path_or_fileobj=src, path_in_repo=f\"fold{fold}_3d.pth\",\n                    repo_id=REPO, repo_type=\"model\")\n    print(f\"3D fold {fold} uploaded ✅\")\n\nfor fold in [4, 5]:\n    src = f\"{v2_path}/aneurysm_fold{fold}.pth\"\n    api.upload_file(path_or_fileobj=src, path_in_repo=f\"fold{fold}_3d.pth\",\n                    repo_id=REPO, repo_type=\"model\")\n    print(f\"3D fold {fold} uploaded ✅\")\n\nprint(f\"\\n🎉 All 10 models uploaded to: https://huggingface.co/{REPO}\")","metadata":{"_uuid":"3fd17b20-66ed-44a1-905c-e2d13be76cfe","_cell_guid":"3bad4cd7-c970-4e91-9767-1d5a9c32fdf0","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T17:59:03.705931Z","iopub.execute_input":"2026-04-14T17:59:03.706222Z","iopub.status.idle":"2026-04-14T18:00:04.052001Z","shell.execute_reply.started":"2026-04-14T17:59:03.706202Z","shell.execute_reply":"2026-04-14T18:00:04.051397Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile /kaggle/working/app.py\nimport os, cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\nimport nibabel as nib\nfrom PIL import Image\nfrom huggingface_hub import hf_hub_download\nimport gradio as gr\n\nDEVICE = torch.device(\"cpu\")\nREPO   = \"diyasilawat/aneurysm-models\"\n\nLABEL_COLS = [\n    \"Left Infraclinoid ICA\",\"Right Infraclinoid ICA\",\n    \"Left Supraclinoid ICA\",\"Right Supraclinoid ICA\",\n    \"Left MCA\",\"Right MCA\",\n    \"Anterior Communicating\",\"Left ACA\",\"Right ACA\",\n    \"Left Post Communicating\",\"Right Post Communicating\",\n    \"Basilar Tip\",\"Other Posterior\",\"Aneurysm Present\"\n]\n\nclass ConvBlock3D(nn.Module):\n    def __init__(self,in_ch,out_ch):\n        super().__init__()\n        self.block=nn.Sequential(\n            nn.Conv3d(in_ch,out_ch,3,padding=1,bias=False),\n            nn.BatchNorm3d(out_ch),nn.ReLU(inplace=True),\n            nn.Conv3d(out_ch,out_ch,3,padding=1,bias=False),\n            nn.BatchNorm3d(out_ch),nn.ReLU(inplace=True),\n        )\n    def forward(self,x): return self.block(x)\n\nclass AneurysmNet(nn.Module):\n    def __init__(self,num_classes=14):\n        super().__init__()\n        self.pool=nn.MaxPool3d(2)\n        self.enc1=ConvBlock3D(1,32);  self.enc2=ConvBlock3D(32,64)\n        self.enc3=ConvBlock3D(64,128);self.enc4=ConvBlock3D(128,256)\n        self.bottleneck=ConvBlock3D(256,512)\n        self.up4=nn.ConvTranspose3d(512,256,2,stride=2)\n        self.dec4=ConvBlock3D(512,256)\n        self.up3=nn.ConvTranspose3d(256,128,2,stride=2)\n        self.dec3=ConvBlock3D(256,128)\n        self.seg_head=nn.Conv3d(128,1,1)\n        self.cls_head=nn.Sequential(\n            nn.AdaptiveAvgPool3d(1),nn.Flatten(),\n            nn.Linear(512,256),nn.LayerNorm(256),\n            nn.GELU(),nn.Dropout(0.3),nn.Linear(256,num_classes),\n        )\n    def forward(self,x):\n        e1=self.enc1(x);e2=self.enc2(self.pool(e1))\n        e3=self.enc3(self.pool(e2));e4=self.enc4(self.pool(e3))\n        b=self.bottleneck(self.pool(e4))\n        cls_out=self.cls_head(b)\n        d4=self.dec4(torch.cat([self.up4(b),e4],dim=1))\n        d3=self.dec3(torch.cat([self.up3(d4),e3],dim=1))\n        seg_out=F.interpolate(self.seg_head(d3),size=x.shape[2:],\n                              mode=\"trilinear\",align_corners=False)\n        return seg_out,cls_out\n\nprint(\"Loading 2D models...\")\nmodels_2d=[]\nfor i in range(5):\n    p=hf_hub_download(REPO,f\"fold{i}_2d.pth\")\n    m=timm.create_model(\"tf_efficientnetv2_s\",pretrained=False,num_classes=14,in_chans=32)\n    ckpt=torch.load(p,map_location=\"cpu\",weights_only=False)\n    m.load_state_dict(ckpt[\"model\"])\n    m.eval();models_2d.append(m)\n    print(f\"  2D fold {i} ready ✅\")\n\nprint(\"Loading 3D models...\")\nmodels_3d=[]\nfor fold in [1,2,3,4,5]:\n    try:\n        p=hf_hub_download(REPO,f\"fold{fold}_3d.pth\")\n        ckpt=torch.load(p,map_location=\"cpu\",weights_only=False)\n        m=AneurysmNet(14)\n        m.load_state_dict(ckpt[\"model_state\"])\n        m.eval();models_3d.append(m)\n        print(f\"  3D fold {fold} ready ✅\")\n    except Exception as e:\n        print(f\"  3D fold {fold} skipped: {e}\")\n\nprint(f\"\\nEnsemble: {len(models_2d)} 2D + {len(models_3d)} 3D = {len(models_2d)+len(models_3d)} total models\")\n\ndef get_brain_slice_idx(total):\n    # Brain is always in upper 55-70% of a head scan volume  ← FIXED\n    return int(total * 0.65)\n\ndef prep_2d(vol):\n    total=vol.shape[2]\n    mid=get_brain_slice_idx(total)        # ← FIXED: use brain region\n    start=max(0,mid-16);end=min(total,start+32)\n    if end-start<32: start=max(0,end-32)\n    sl=vol[:,:,start:end].copy()\n    for i in range(32):\n        s=np.clip(sl[:,:,i],-100,700)\n        s=(s-s.min())/(s.max()-s.min()+1e-6);sl[:,:,i]=s\n    r=np.stack([cv2.resize(sl[:,:,i],(224,224)) for i in range(32)],axis=0)\n    return torch.tensor(r).unsqueeze(0).float()\n\ndef prep_3d(vol):\n    t=torch.from_numpy(vol.copy()[np.newaxis,np.newaxis]).float()\n    t=F.interpolate(t,size=(96,96,96),mode=\"trilinear\",align_corners=False)\n    v=t[0,0].numpy()\n    p1,p99=np.percentile(v,[1,99])\n    v=(np.clip(v,p1,p99)-p1)/(p99-p1+1e-8)\n    return torch.from_numpy(v[np.newaxis,np.newaxis]).float()\n\ndef predict(nifti_file):\n    vol=nib.load(nifti_file.name).get_fdata().astype(np.float32)\n\n    # Brain preview using correct slice region   ← FIXED\n    brain_idx=get_brain_slice_idx(vol.shape[2])\n    mid=vol[:,:,brain_idx]\n    mid=np.clip(mid,-100,700)\n    mid=(mid-mid.min())/(mid.max()-mid.min()+1e-6)\n    brain=np.stack([cv2.resize((mid*255).astype(np.uint8),(224,224))]*3,axis=-1)\n\n    t2d=prep_2d(vol)\n    f2=[]\n    with torch.no_grad():\n        for m in models_2d:\n            f2.append(torch.sigmoid(m(t2d)).numpy()[0])\n    s2=np.mean(f2,axis=0)\n\n    overlay=Image.fromarray(brain.astype(np.uint8))\n    if models_3d:\n        t3d=prep_3d(vol)\n        f3=[];seg_maps=[]\n        with torch.no_grad():\n            for m in models_3d:\n                seg,cls=m(t3d)\n                f3.append(torch.sigmoid(cls).numpy()[0])\n                seg_maps.append(torch.sigmoid(seg).numpy()[0,0])\n        s3=np.mean(f3,axis=0)\n        seg_avg=np.mean(seg_maps,axis=0)\n        seg_brain_idx=int(seg_avg.shape[0]*0.65)\n        midseg=cv2.resize(seg_avg[seg_brain_idx],(224,224))\n        hm=cv2.applyColorMap((midseg*255).astype(np.uint8),cv2.COLORMAP_JET)\n        hm=cv2.cvtColor(hm,cv2.COLOR_BGR2RGB)\n        ov=cv2.addWeighted(brain,0.5,hm,0.5,0)\n        overlay=Image.fromarray(ov.astype(np.uint8))\n        final=(s2+s3)/2.0\n        mode=f\"2D×{len(models_2d)} + 3D×{len(models_3d)} Ensemble\"\n    else:\n        final=s2\n        mode=f\"2D×{len(models_2d)} Only\"\n\n    sc=float(final[-1])\n    lbl=\"🔴 HIGH RISK\" if sc>0.6 else \"🟡 MODERATE\" if sc>0.35 else \"🟢 LOW RISK\"\n    res=f\"MODE: {mode}\\nSCORE: {sc:.3f} → {lbl}\\n\\n── Per Location ──\\n\"\n    for n,sf in zip(LABEL_COLS,final):\n        b=\"🔴\" if sf>0.6 else \"🟡\" if sf>0.35 else \"🟢\"\n        res+=f\"{b} {n:<28} {sf:.3f}\\n\"\n    res+=\"\\n⚠️ Research prototype. Not for clinical use.\"\n    return Image.fromarray(brain.astype(np.uint8)),overlay,res\n\ngr.Interface(\n    fn=predict,\n    inputs=gr.File(label=\"Upload Brain CT (.nii or .nii.gz)\"),\n    outputs=[\n        gr.Image(label=\"Brain Slice\"),\n        gr.Image(label=\"🔥 Heatmap Overlay\"),\n        gr.Textbox(label=\"Risk Scores\",lines=22)\n    ],\n    title=\"🧠 Aneurysm Risk Screener\",\n    description=\"2D EfficientNetV2-S ×5 + 3D AneurysmNet ×5 | Full Ensemble | Permanent\"\n).launch()","metadata":{"_uuid":"fce655cb-d2ea-414b-a3cc-05d1a8ec57fd","_cell_guid":"6d11bd78-849d-4d69-8f85-379eba33fe97","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T18:24:02.23877Z","iopub.execute_input":"2026-04-14T18:24:02.239001Z","iopub.status.idle":"2026-04-14T18:24:02.245511Z","shell.execute_reply.started":"2026-04-14T18:24:02.238988Z","shell.execute_reply":"2026-04-14T18:24:02.244907Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile /kaggle/working/requirements.txt\ngradio\ntorch\ntimm\nnibabel\nopencv-python-headless\nhuggingface_hub\nnumpy\nPillow","metadata":{"_uuid":"b12442db-97a2-4894-a42a-efaa1f4c429c","_cell_guid":"2a921e6c-2b8a-4840-83c2-cf77a688bae0","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T18:00:04.060477Z","iopub.execute_input":"2026-04-14T18:00:04.060689Z","iopub.status.idle":"2026-04-14T18:00:04.080843Z","shell.execute_reply.started":"2026-04-14T18:00:04.060668Z","shell.execute_reply":"2026-04-14T18:00:04.079706Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.path.exists(\"/kaggle/working/app.py\"))\nprint(os.path.exists(\"/kaggle/working/requirements.txt\"))","metadata":{"_uuid":"46f292f3-d5f9-4638-8439-b2586f805c5d","_cell_guid":"fd38b03c-214f-4a6d-aa90-dc0f1e3dfc0e","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T18:00:04.082208Z","iopub.execute_input":"2026-04-14T18:00:04.083287Z","iopub.status.idle":"2026-04-14T18:00:04.098482Z","shell.execute_reply.started":"2026-04-14T18:00:04.083222Z","shell.execute_reply":"2026-04-14T18:00:04.097625Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from huggingface_hub import HfApi, login\n\nlogin(token=\"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\")\napi = HfApi()\n\nHF_SPACE = \"diyasilawat/aneurysm-screener\"\n\napi.upload_file(\n    path_or_fileobj=\"/kaggle/working/app.py\",\n    path_in_repo=\"app.py\",\n    repo_id=\"diyasilawat/aneurysm-screener\",\n    repo_type=\"space\"\n)\nprint(\"requirements.txt uploaded ✅\")\nprint(\"\\n🚀 Live at: https://huggingface.co/spaces/diyasilawat/aneurysm-screener\")","metadata":{"_uuid":"4b4b4220-0a6f-4aed-a697-e4e7413a7210","_cell_guid":"8c8ce778-3334-4038-a968-038eb5c13e8f","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-04-14T18:26:57.458238Z","iopub.execute_input":"2026-04-14T18:26:57.458475Z","iopub.status.idle":"2026-04-14T18:26:58.564763Z","shell.execute_reply.started":"2026-04-14T18:26:57.458457Z","shell.execute_reply":"2026-04-14T18:26:58.564148Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"a6a11f2d-008b-40fb-9881-5c32dc5ebf06","_cell_guid":"1b5388cf-48ea-4189-8e0f-4aa083d7d906","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}