{"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":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"\nStarter PyTorch notebook for PhysioNet ECG Image Digitization competition.\nUpdated for direct Kaggle submission (produces /kaggle/working/submission.csv).\n\n- Runs offline within <9h (train locally, run inference here).\n- Writes submission file in correct format for 'Submit' button activation.\n\nCreator: Livin Viji Varghese\n\"\"\"\n\nimport os\nimport math\nimport glob\nimport random\nfrom typing import List, Tuple\n\nimport numpy as np\nfrom PIL import Image, ImageOps, ImageFilter\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\n# -----------------------------\n# Config\n# -----------------------------\nclass CFG:\n    seed = 42\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    img_size = (512, 512)\n    lead_crop_size = (256, 64)\n    samples_per_lead = 5000\n    batch_size = 8\n    epochs = 5\n    lr = 1e-4\n    max_shift_seconds = 0.2\n    sampling_rate = 500\n    data_dir = './data'\n    images_dir = os.path.join(data_dir, 'images')\n    signals_dir = os.path.join(data_dir, 'signals')\n    submission_path = '/kaggle/working/submission.csv'\n\nrandom.seed(CFG.seed)\nnp.random.seed(CFG.seed)\ntorch.manual_seed(CFG.seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(CFG.seed)\n\n# -----------------------------\n# Basic preprocessing utils\n# -----------------------------\ndef load_image(path: str):\n    return Image.open(path).convert('RGB')\n\ndef simple_autocrop_page(image):\n    return ImageOps.contain(image, CFG.img_size)\n\ndef estimate_lead_boxes_fixed_template(page_size: Tuple[int,int]) -> List[Tuple[int,int,int,int]]:\n    W,H = page_size\n    rows, cols = 3, 4\n    bw, bh = W//cols, H//rows\n    boxes = []\n    for r in range(rows):\n        for c in range(cols):\n            boxes.append((c*bw, r*bh, (c+1)*bw, (r+1)*bh))\n    return boxes\n\ndef crop_lead(page, box):\n    x1,y1,x2,y2 = box\n    c = page.crop((x1,y1,x2,y2)).resize(CFG.lead_crop_size)\n    return c\n\ndef preprocess_lead_image(img: Image.Image) -> np.ndarray:\n    gray = img.convert('L')\n    arr = np.array(gray).astype(np.float32)/255.0\n    arr = (arr - 0.5)*2.0\n    return arr\n\n# -----------------------------\n# Simple model\n# -----------------------------\nclass PerLeadNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.encoder = nn.Sequential(\n            nn.Conv2d(1,32,3,2,1), nn.ReLU(),\n            nn.Conv2d(32,64,3,2,1), nn.ReLU(),\n            nn.Conv2d(64,128,3,2,1), nn.ReLU(),\n            nn.AdaptiveAvgPool2d((1,1))\n        )\n        self.fc = nn.Sequential(\n            nn.Linear(128,256), nn.ReLU(),\n            nn.Linear(256,CFG.samples_per_lead)\n        )\n\n    def forward(self, x):\n        B,L,H,W = x.shape\n        x = x.view(B*L,1,H,W)\n        z = self.encoder(x).reshape(B*L,-1)\n        out = self.fc(z)\n        return out.view(B,L,-1)\n\n# -----------------------------\n# Inference + submission\n# -----------------------------\ndef infer_and_write_submission(model, images_dir=CFG.images_dir, out_path=CFG.submission_path):\n    model.eval()\n    image_files = sorted(glob.glob(os.path.join(images_dir,'*.png')) + glob.glob(os.path.join(images_dir,'*.jpg')))\n    rows = []\n    lead_names = ['I','II','III','aVR','aVL','aVF','V1','V2','V3','V4','V5','V6']\n    with torch.no_grad():\n        for p in image_files:\n            image_id = os.path.basename(p).split('.')[0]\n            page = simple_autocrop_page(load_image(p))\n            boxes = estimate_lead_boxes_fixed_template(page.size)\n            crops = [preprocess_lead_image(crop_lead(page,b)) for b in boxes[:12]]\n            arr = np.stack(crops,axis=0)[None,...]\n            imgs_t = torch.tensor(arr,dtype=torch.float32).to(CFG.device)\n            preds = model(imgs_t).cpu().numpy()[0]\n            for li in range(12):\n                rid = f\"{image_id}_{li}_{lead_names[li]}\"\n                val = float(np.mean(preds[li]))\n                rows.append((rid,val))\n    df = pd.DataFrame(rows, columns=['id','value'])\n    df.to_csv(out_path, index=False)\n    print(f\"✅ Wrote submission file to {out_path} ({len(df)} rows)\")\n    print(df.head())\n\n# -----------------------------\n# Main entry (lightweight demo)\n# -----------------------------\ndef main():\n    model = PerLeadNet().to(CFG.device)\n    if os.path.exists('perlead_model.pt'):\n        model.load_state_dict(torch.load('perlead_model.pt', map_location=CFG.device))\n        print('Loaded pre-trained weights.')\n    else:\n        print('No weights found, using randomly initialized model (for demo).')\n    infer_and_write_submission(model)\n\nif __name__ == '__main__':\n    main()\n\n# -----------------------------\n# This notebook now:\n# - runs fully offline (no internet)\n# - finishes within a few minutes for inference only\n# - writes /kaggle/working/submission.csv\n# After commit, the Kaggle \"Submit\" button will activate automatically.\n# -----------------------------\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T05:13:14.810969Z","iopub.execute_input":"2025-11-13T05:13:14.811241Z","iopub.status.idle":"2025-11-13T05:13:24.272099Z","shell.execute_reply.started":"2025-11-13T05:13:14.811212Z","shell.execute_reply":"2025-11-13T05:13:24.271116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}