{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","dataSources":[{"sourceId":97984,"databundleVersionId":14096757,"sourceType":"competition"},{"sourceId":13907058,"sourceType":"datasetVersion","datasetId":8860757},{"sourceId":13988749,"sourceType":"datasetVersion","datasetId":8916242},{"sourceId":14065576,"sourceType":"datasetVersion","datasetId":8952899},{"sourceId":14201126,"sourceType":"datasetVersion","datasetId":9056846},{"sourceId":14260367,"sourceType":"datasetVersion","datasetId":9099494},{"sourceId":14405220,"sourceType":"datasetVersion","datasetId":9200301},{"sourceId":14510730,"sourceType":"datasetVersion","datasetId":9268099},{"sourceId":291753261,"sourceType":"kernelVersion"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Bartley Part","metadata":{}},{"cell_type":"code","source":"import os\nimport sys\nimport shutil\n\nKAGGLE = 'KAGGLE_URL_BASE' in os.environ\nSUBMISSION = 'KAGGLE_IS_COMPETITION_RERUN' in os.environ or os.environ.get('KAGGLE_KERNEL_RUN_TYPE') == 'Batch'\nSUBMISSION = True\n\nif KAGGLE:\n    os.environ['CUDA_VISIBLE_DEVICES']= \"0,1\"\n    !python -m pip install --no-index --no-deps --find-links=/kaggle/input/offline-package-install -r /kaggle/input/offline-package-install/requirements.txt\n    sys.path.append('/kaggle/input/physionet-2025-submission/')\n    MODEL_DIR = \"/kaggle/input/physionet-2025-submission/models/\"\n\nelse:\n    os.environ['CUDA_VISIBLE_DEVICES']= \"0,1\"\n    sys.path.append('./logs_final/')\n    MODEL_DIR = \"./logs_final/models/\"\n\n    if os.path.exists(\"./logs_infer/tmp\"):\n        shutil.rmtree(\"./logs_infer/tmp\")\n    os.makedirs(\"./logs_infer/tmp\", exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:34:12.519695Z","iopub.execute_input":"2026-01-15T20:34:12.520246Z","iopub.status.idle":"2026-01-15T20:34:18.306513Z","shell.execute_reply.started":"2026-01-15T20:34:12.520225Z","shell.execute_reply":"2026-01-15T20:34:18.305796Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Infer Part 1","metadata":{}},{"cell_type":"code","source":"%%writefile _infer_part1.py\n\nimport os\nimport torch\nimport sys\nimport gc\nimport ast\nimport glob\nfrom copy import deepcopy\nfrom types import SimpleNamespace\nfrom importlib import import_module\nfrom functools import partial\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nimport joblib\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.distributed as dist\n\nos.environ['NO_ALBUMENTATIONS_UPDATE'] = '1'\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0,1\"\nprint(f\"CUDA_VISIBLE_DEVICES: {os.environ.get('CUDA_VISIBLE_DEVICES')}\")\nprint(f\"GPU Count Seen by Python: {torch.cuda.device_count()}\")\n\nsys.path.append('/kaggle/input/physionet-2025-submission/')\nsys.path.append('./logs_final/')\n\nfrom src.utils.torch import batch_to_device, count_parameters, crop_2d\nfrom src.models.utils import get_model\nfrom src.augs.albu import get_transfos\n\n\n#########################\n#### Ensemble Models ####\n#########################\n\ndef load_ensemble_models(model_dirs, cfg):\n    models = []\n    for mdir in model_dirs:\n        model_cfg = joblib.load(os.path.join(mdir, \"cfg.pkl\"))\n\n        # Load fullfit first if available\n        mpath = glob.glob(os.path.join(mdir, \"*fullfit*.pt\")) + glob.glob(os.path.join(mdir, \"*.pt\"))\n        model_cfg.load_cfg.wpath = mpath[0]\n        model_cfg.load_cfg.skip_str = None\n        model_cfg.compile = False\n        \n        model = get_model(model_cfg, inference_mode=True)\n        model = model.to(cfg.local_rank).eval()\n        models.append(model)\n        \n        # Clean up cfg\n        del model_cfg\n    \n    return models\n\nclass EnsembleModel(nn.Module):\n    def __init__(self, models, activation=None, method='mean'):\n        super().__init__()\n        self.models = nn.ModuleList(models)\n        self.activation = activation\n        self.method = method\n\n    def forward(self, x):\n        outputs = []\n        for model in self.models:\n            logits = model(x)\n            if self.activation == 'softmax':\n                logits = torch.softmax(logits, dim=-1)\n            elif self.activation == 'sigmoid':\n                logits = torch.sigmoid(logits)\n            outputs.append(logits)\n        \n        stacked = torch.stack(outputs)\n        if self.method == 'mean':\n            return stacked.mean(dim=0)\n        elif self.method == 'median':\n            return stacked.median(dim=0).values\n        elif self.method == 'mode':\n            return stacked.mode(dim=0).values\n        elif self.method == 'max':\n            return stacked.max(dim=0).values\n        else:\n            raise ValueError(f\"Unknown method: {self.method}\")\n\nclass EnsembleModelDET(nn.Module):\n    def __init__(self, models):\n        super().__init__()\n        self.models = nn.ModuleList(models)\n\n    def forward(self, x, height, width, aoi, threshold=0.25):\n        outputs = []\n        outputs_mask = []\n\n        for model in self.models:\n            logits, mask = model(x)\n\n            # Lead boxes\n            logits[:, 4:] = F.sigmoid(logits[:, 4:])\n            logits = model.decode_predictions_argmax(\n                y_pred=logits,\n                height=height,\n                width=width,\n                aoi=aoi,\n            )\n            outputs.append(logits)\n\n            # Lead masks\n            mask = F.sigmoid(mask)\n            outputs_mask.append(mask)\n        \n        # Ensemble\n        outputs = torch.stack(outputs)\n        outputs = outputs.median(dim=0).values\n\n        outputs_mask = torch.stack(outputs_mask)\n        outputs_mask = outputs_mask.mean(dim=0)\n\n        # Convert to labels\n        background_mask = (torch.zeros_like(outputs_mask[:, :1, ...], device=outputs_mask.device) + threshold) # background class\n        outputs_mask = torch.cat([background_mask, outputs_mask], dim=1) # stack\n\n        return outputs, outputs_mask\n\n##################\n#### Rotation ####\n##################\n\ndef predict_rotate(\n    cfg,\n    model,\n    test_loader,\n    mixed_precision=True,\n    distributed=False,\n    world_size=0,\n    local_rank=0,\n):\n    model.eval()\n    preds = []\n\n    with torch.no_grad():\n        for batch in tqdm(test_loader, disable=local_rank != 0):\n            with torch.amp.autocast(cfg.device.type, enabled=mixed_precision):\n\n                if distributed:\n                    batch= batch_to_device(batch, device=cfg.local_rank)\n                else:\n                    batch= batch_to_device(batch, device=cfg.device)\n                \n                x = batch[\"input\"].float()\n                y_pred = model(x)\n                y_pred = torch.argmax(y_pred, dim=1)\n\n            preds.append(y_pred.cpu())\n    preds = torch.cat(preds, dim=0)\n\n    if local_rank == 0:\n        preds = preds.cpu().numpy()\n        return preds\n    else:\n        return 0\n\n\ndef run_rotate(\n    cfg: SimpleNamespace,\n    model_dirs: list[str],\n    df_infer: pd.DataFrame,\n    mixed_precision=True,\n    distributed=False,\n    local_rank=0,\n    world_size=1,\n    log_folder=None,\n    fold=0,\n): \n    # Reinit logger\n    cfg.fold = fold\n\n    # Dataset class\n    dpath= f\"src.data.{cfg.dataset}\"\n    dataset_class = import_module(dpath).CustomDataset\n\n    # Dataset - Infer\n    transfos_test = get_transfos(augment=False, **vars(cfg.albu_cfg))\n    test_dataset = dataset_class(\n        cfg=cfg,\n        df=df_infer,\n        transforms=transfos_test,\n        train=False,\n        infer=True,\n    )\n\n    # Dataloaders\n    test_loader = torch.utils.data.DataLoader(\n        test_dataset,\n        batch_size=2,\n        shuffle=False,\n        num_workers=2,\n        pin_memory=True,\n        persistent_workers=False,\n        collate_fn=test_dataset.collate_fn,\n    )\n\n    # Load model ensemble\n    models = load_ensemble_models(model_dirs, cfg)\n    model = EnsembleModel(models=models, activation=\"softmax\")\n    model = model.to(cfg.local_rank).eval()\n    n_params = count_parameters(model)\n\n    if cfg.local_rank == 0:\n        print(f\"    -> {len(test_dataset)} test samples\")\n        print(f\"    -> {n_params:_} trainable parameters\\n\")\n\n    # Eval\n    preds = predict_rotate(\n        cfg=cfg,\n        model=model,\n        test_loader=test_loader,\n        mixed_precision=mixed_precision,\n        distributed=distributed,\n        world_size=world_size,\n        local_rank=local_rank,\n    )\n\n    # Save OOF preds\n    if (log_folder is not None) and (cfg.save_cfg.weights) and (cfg.local_rank == 0):\n        df_infer[\"rot90\"] = preds\n\n    torch.cuda.empty_cache()\n    gc.collect()\n\n    if distributed:\n        torch.distributed.barrier()\n\n    return df_infer\n\n###################\n#### Detection ####\n###################\n\ndef predict_det(\n    cfg,\n    model,\n    test_loader,\n    mixed_precision=True,\n    distributed=False,\n    world_size=0,\n    local_rank=0,\n    save_masks=False,\n):\n    model.eval()\n    preds = []\n    heights = []\n    widths = []\n\n\n    with torch.no_grad():\n        for batch in tqdm(test_loader, disable=local_rank != 0):\n            with torch.amp.autocast(cfg.device.type, enabled=mixed_precision):\n\n                if distributed:\n                    batch= batch_to_device(batch, device=cfg.local_rank)\n                else:\n                    batch= batch_to_device(batch, device=cfg.device)\n                \n                x = batch[\"input\"].float()\n                height= batch[\"height\"].int()\n                width= batch[\"width\"].int()\n                aoi= batch[\"aoi\"].int()\n\n                # Note: Box conversion done inside ensemble model\n                y_pred, y_mask = model(x, height=height, width=width, aoi=aoi)\n            \n                # Post process lead II full box (some boxes are too shallow on height)\n                # Sets minimum height as median height of other boxes\n                box_heights = y_pred[:, :, 3] - y_pred[:, :, 1]\n                box_median_heights = torch.median(box_heights[:, :-1], dim=1).values\n                mask_expand = box_heights[:, -1] < box_median_heights\n                diff = box_median_heights - box_heights[:, -1]\n                half_diff = (diff / 2).int()\n                y_pred[mask_expand, -1, 1] -= half_diff[mask_expand]\n                y_pred[mask_expand, -1, 3] += half_diff[mask_expand]\n\n                y_pred = y_pred.cpu().numpy()\n\n                # Saves segmentation predictions\n                if save_masks:\n                    for i in range(x.shape[0]):\n                        \n                        # Extract heights/widths\n                        h_full = batch[\"height_full\"][i].item()\n                        w_full = batch[\"width_full\"][i].item()\n                        h = height[i].item()\n                        w = width[i].item()\n                        x1, y1, x2, y2 = aoi[i].tolist()\n                        # assert y2-y1 == h and x2-x1 == w\n                        \n                        # Resize mask\n                        mask_resized = torch.nn.functional.interpolate(\n                            y_mask[i:i+1].float(),\n                            size=(h, w),  # Resize to AOI\n                            mode='bilinear',\n                        ).squeeze(0)\n                        mask_resized = mask_resized[1:] # remove background\n\n                        # Insert AOI\n                        mask = torch.zeros((13, h_full, w_full), dtype=torch.float16, device=mask_resized.device)\n                        mask[:, y1:y2, x1:x2] = mask_resized\n\n                        # print(y_pred)\n                        # print(y_pred.tolist())\n                        \n                        # Crop leads\n                        for j in range(mask.shape[0]):\n                            \n                            # Get crop scale\n                            if CLASSES[j] == \"II_full\":\n                                scale = (WSCALE_II_FULL, HSCALE_II_FULL)\n                            else:\n                                scale = (WSCALE, HSCALE)\n\n                            # Crop around lead\n                            crop = crop_2d(\n                                img = batch[\"img\"][i], \n                                box = y_pred[i][j].tolist(),\n                                scale = scale,\n                                min_x_px = 64,\n                                min_y_px = 16,\n                            )\n                            mask_crop = crop_2d(\n                                img = mask, \n                                box = y_pred[i][j].tolist(),\n                                scale = scale,\n                                min_x_px = 64,\n                                min_y_px = 16,\n                            )\n                            \n                            # Get lead prob, and max prob of other leads\n                            mask_crop = torch.stack([\n                                mask_crop[j], # Lead prob\n                                mask_crop.clone().index_fill_(0, torch.tensor(j, device=mask_crop.device), -float('inf')) \\\n                                    .max(dim=0).values # Max of other lead prob\n                            ], dim=0)\n                            mask_crop = (mask_crop * 255.0).to(torch.uint8) # Covnert probs to pixel space\n\n                            # Cat to 5 channels\n                            lead_crop = torch.cat([crop, mask_crop], dim=0)\n                            lead_crop = lead_crop.cpu().numpy()\n\n                            # Save\n                            outpath = os.path.join(MASK_DIR, f\"{batch['id'][i]}_{batch['img_type'][i]}_{CLASSES[j]}.npy\")\n                            np.save(outpath, lead_crop)\n\n            preds.append(y_pred)\n            heights.extend(height.cpu().int().numpy())\n            widths.extend(width.cpu().int().numpy())\n    \n    preds = np.concatenate(preds, axis=0)\n\n    if local_rank == 0:\n        return preds, heights, widths\n    else:\n        return 0\n\n\ndef run_det(\n    cfg: SimpleNamespace,\n    model_dirs: list[str],\n    df_infer: pd.DataFrame,\n    mixed_precision=True,\n    distributed=False,\n    local_rank=0,\n    world_size=1,\n    log_folder=None,\n    fold=0,\n    save_masks=False,\n    mask_dir=None\n): \n    # Reinit logger\n    cfg.fold = fold\n\n    # Dataset class\n    dpath= f\"src.data.{cfg.dataset}\"\n    dataset_class = import_module(dpath).CustomDataset\n\n    # Dataset - Infer\n    transfos_test = get_transfos(augment=False, **vars(cfg.albu_cfg))\n    test_dataset = dataset_class(\n        cfg=cfg,\n        df=df_infer,\n        transforms=transfos_test,\n        train=False,\n        infer=True,\n    )\n\n    # Dataloaders\n    test_loader = torch.utils.data.DataLoader(\n        test_dataset,\n        batch_size=2, \n        shuffle=False,\n        num_workers=2,\n        pin_memory=True,\n        persistent_workers=False,\n        collate_fn=test_dataset.collate_fn,\n    )\n\n    # Load model ensemble\n    models = load_ensemble_models(model_dirs, cfg)\n    model = EnsembleModelDET(models=models)\n    model = model.to(cfg.local_rank).eval()\n    n_params = count_parameters(model)\n\n    if cfg.local_rank == 0:\n        print(f\"    -> {len(test_dataset)} test samples\")\n        print(f\"    -> {n_params:_} trainable parameters\\n\")\n\n    # Eval\n    preds, heights, widths = predict_det(\n        cfg=cfg,\n        model=model,\n        test_loader=test_loader,\n        mixed_precision=mixed_precision,\n        distributed=distributed,\n        world_size=world_size,\n        local_rank=local_rank,\n        save_masks=save_masks,\n    )\n\n    # Save OOF preds\n    if (log_folder is not None) and (cfg.save_cfg.weights) and (cfg.local_rank == 0):\n        res = []\n\n        # Save row per box\n        for i in range(preds.shape[0]):\n            row = df_infer.iloc[i]\n            meta = deepcopy(row[\"meta\"]) # important to copy here\n            h = heights[i]\n            w = widths[i]\n\n            for j in range(preds.shape[1]):\n                meta[j][\"box\"] = preds[i, j].tolist()\n\n            res.append({\n                \"id\": row[\"id\"],\n                \"img_type\": row[\"img_type\"],\n                \"height\": h,\n                \"width\": w,\n                \"rot90\": row[\"rot90\"],\n                \"meta\": meta,\n            })\n\n        df_out = pd.DataFrame(res)\n\n    torch.cuda.empty_cache()\n    gc.collect()\n\n    if distributed:\n        torch.distributed.barrier()\n\n    return df_out\n\n\nif __name__ == \"__main__\":\n\n    ############################\n    ##### Distributed Init #####\n    ############################\n    if \"LOCAL_RANK\" in os.environ:\n        local_rank = int(os.environ[\"LOCAL_RANK\"])\n        rank = int(os.environ[\"RANK\"])\n        world_size = int(os.environ[\"WORLD_SIZE\"])\n        \n        # Force strict isolation so \"cuda:0\" always maps to the local GPU\n        os.environ[\"CUDA_VISIBLE_DEVICES\"] = str(local_rank)\n        \n        dist.init_process_group(backend=\"nccl\")\n        torch.cuda.set_device(0)\n        print(f\"[Init] Rank {rank}/{world_size} initialized on isolated GPU {local_rank}\")\n    else:\n        rank = 0\n        local_rank = 0\n        world_size = 1\n        print(\"Running in standard single-process mode.\")\n\n    ##################\n    ##### Params #####\n    ##################\n    CLASSES = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'II_full']\n    CLASSES2IDX = {v:k+1 for k,v in enumerate(CLASSES)} \n    IDX2CLASSES = {v:k for k,v in CLASSES2IDX.items()}\n\n    KAGGLE = 'KAGGLE_URL_BASE' in os.environ\n    SUBMISSION = os.environ['SUBMISSION'] == \"True\"\n    MODEL_DATASET = os.environ.get(\"MODEL_DATASET\", None)\n    MODEL_RESIZE = eval(os.environ.get(\"MODEL_RESIZE\", \"(1,2,3)\"))\n    MODEL_RESIZE_STR = \"_\".join(map(str, MODEL_RESIZE)) if MODEL_RESIZE else None\n\n    WSCALE = 1.05\n    HSCALE = 1.3\n    WSCALE_II_FULL = 1.05\n    HSCALE_II_FULL = 2.15\n    AOI_FACTOR = 2.25\n\n    if rank == 0:\n        print(f\"[PARAMS] SUBMISSION={SUBMISSION}, KAGGLE={KAGGLE}\")\n        print(f\"[SCALES] WSCALE={WSCALE}, HSCALE={HSCALE}, WSCALE_II_FULL={WSCALE_II_FULL}, HSCALE_II_FULL={HSCALE_II_FULL}, AOI_FACTOR={AOI_FACTOR}\")\n        print(f\"[OTHER] MODEL_DATASET={MODEL_DATASET}, MODEL_RESIZE={MODEL_RESIZE}\")\n        \n    if KAGGLE:\n        MODEL_DIR = \"/kaggle/input/physionet-2025-submission/models/\"\n        LOG_DIR = \"/kaggle/working/\"\n        MASK_DIR = \"/kaggle/working/masks/\"\n        PRED_DIR = \"/kaggle/working/preds/\"\n\n        if SUBMISSION:\n            DATA_DIR = \"/kaggle/input/physionet-ecg-image-digitization/test/\"\n            META_PATH = \"/kaggle/input/physionet-ecg-image-digitization/test.csv\"\n        else:\n            DATA_DIR = \"/kaggle/input/physionet-ecg-image-digitization/train/\"\n            META_PATH = \"/kaggle/input/physionet-2025-submission/train.csv\"\n            LABEL_PATH = \"/kaggle/input/physionet-2025-submission/labels.csv\"\n            FOLDS_PATH = \"/kaggle/input/physionet-2025-submission/folds.csv\"\n    else:\n        MODEL_DIR = \"./logs_final/models/\"\n        LOG_DIR = \"./logs_infer/tmp/\"\n        MASK_DIR = \"./logs_infer/tmp/masks/\"\n        PRED_DIR = \"./logs_infer/tmp/preds/\"\n\n        if SUBMISSION:\n            DATA_DIR = \"./data/raw/test/\"\n            META_PATH = \"./data/raw/test.csv\"\n        else:\n            DATA_DIR = \"./data/raw/train/\"\n            META_PATH = \"./logs_final/train.csv\"\n            LABEL_PATH = \"./logs_final/labels.csv\"\n            FOLDS_PATH = \"./logs_final/folds.csv\"\n    \n    os.makedirs(LOG_DIR, exist_ok=True)\n    os.makedirs(MASK_DIR, exist_ok=True)\n    os.makedirs(PRED_DIR, exist_ok=True)\n\n\n    #####################\n    ##### Metadata ######\n    #####################\n    if SUBMISSION:\n        N_SAMPLES = 1_000_000\n    else:\n        N_SAMPLES = 4\n    IMG_TYPES = ['0006']\n\n\n    meta_df = pd.read_csv(META_PATH)\n\n    # Add rows for II and II_full\n    meta_df_II = meta_df.loc[meta_df[\"lead\"] == \"II\", :].copy()\n    meta_df_II[\"number_of_rows\"] = meta_df_II[\"number_of_rows\"] // 4\n    meta_df.loc[meta_df[\"lead\"] == \"II\", \"lead\"] = \"II_full\"\n    meta_df = pd.concat([meta_df, meta_df_II], axis=0)\n    del meta_df_II\n\n    # Add cls/cls_idx\n    meta_df[\"cls\"] = meta_df[\"lead\"]\n    meta_df[\"cls_idx\"] = meta_df[\"cls\"].map(CLASSES2IDX)\n    meta_df = meta_df.sort_values([\"id\", \"cls_idx\"]).reset_index(drop=True)\n\n    # Create meta col\n    cols = ['cls', 'cls_idx', 'number_of_rows']\n    meta_df = meta_df.groupby('id')[cols].apply(lambda x: x[cols].to_dict('records')).to_frame('meta').reset_index()\n\n    if not SUBMISSION:\n        # Add folds\n        folds= pd.read_csv(FOLDS_PATH)\n        _len = len(meta_df)\n        meta_df = pd.merge(meta_df, folds, on=[\"id\"], how=\"inner\")\n        assert len(meta_df) == _len\n        meta_df = meta_df[meta_df[\"fold\"] == 0]\n        \n        # Subsample\n        meta_df = meta_df.head(N_SAMPLES)\n    \n    # Split data on each device\n    if world_size > 1:\n        meta_df = meta_df.iloc[rank::world_size]\n        print(f\"[Rank {rank}] Processing {len(meta_df)} samples.\")\n\n    # Add final cols\n    df_infer = []\n    for _, row in meta_df.iterrows():\n        if SUBMISSION:\n            df_infer.append({\n                \"id\": row[\"id\"],\n                \"img_fpath\": os.path.join(DATA_DIR, f\"{row['id']}.png\"),\n                \"mask_fpath\": None,\n                \"img_type\": 0,\n                \"meta\": row[\"meta\"],\n            })\n        else:\n            for img_type in IMG_TYPES:\n                df_infer.append({\n                    \"id\": row[\"id\"],\n                    \"img_fpath\": os.path.join(DATA_DIR, f\"{row['id']}/{row['id']}-{img_type}.png\"),\n                    \"mask_fpath\": None,\n                    \"img_type\": int(img_type),\n                    \"meta\": row[\"meta\"],\n                })\n\n    df_infer = pd.DataFrame(df_infer)\n    \n    #####################\n    ##### Rotation ######\n    #####################\n    model_dirs = glob.glob(os.path.join(MODEL_DIR, \"rotate/*\"))\n    cfg = joblib.load(os.path.join(model_dirs[0], \"cfg.pkl\"))\n\n    print(f\"\\n[Rank {rank}] Starting rotation... len(df_infer) = {len(df_infer):_}\")\n    \n    if len(df_infer) > 0:\n        df_rotate = run_rotate(\n            cfg=cfg,\n            model_dirs=model_dirs,\n            df_infer=df_infer,\n            mixed_precision=True,\n            distributed=False,\n            local_rank=0, # ALWAYS 0 because we used Isolation Trick\n            world_size=1,\n            log_folder=LOG_DIR,\n            fold=0,\n        )\n    else:\n        df_rotate = pd.DataFrame(columns=df_infer.columns)\n\n\n\n    #############################\n    ##### Detection Part 1 ######\n    #############################\n    model_dirs = glob.glob(os.path.join(MODEL_DIR, \"det_1024/*\"))\n    cfg = joblib.load(os.path.join(model_dirs[0], \"cfg.pkl\"))\n    print(f\"\\n[Rank {rank}] Starting lead detection... len(df_rotate) = {len(df_rotate):_}\")\n\n    if len(df_rotate) > 0:\n        df_det = run_det(\n            cfg=cfg,\n            model_dirs=model_dirs,\n            df_infer=df_rotate,\n            mixed_precision=True,\n            distributed=False,\n            local_rank=0, # ALWAYS 0\n            world_size=1,\n            log_folder=LOG_DIR,\n            fold=0,\n            save_masks=False, # Don't save masks on first pass\n            mask_dir=None\n        )\n    else:\n        df_det = pd.DataFrame(columns=df_rotate.columns.tolist() + ['pred', 'conf'])\n\n\n    ############################################\n    ##### Detection Part 2: Refined w/ AOI #####\n    ############################################\n    def get_aoi(row, scale_factor=2.0):\n        \"\"\"\n        Extract scaled area of interest from lead crops.\n\n        Args:\n            row: Pandas dataframe row\n            scale_factor (float, optional): Factor to scale the AOI by. Defaults to 2.0.\n        \"\"\"\n        \n        # Extract lead boxes\n        boxes = row[\"meta\"]\n        all_x1 = [b['box'][0] for b in boxes]\n        all_y1 = [b['box'][1] for b in boxes]\n        all_x2 = [b['box'][2] for b in boxes]\n        all_y2 = [b['box'][3] for b in boxes]\n        \n        # Get AOI\n        x1, y1, x2, y2 = min(all_x1), min(all_y1), max(all_x2), max(all_y2)\n        \n        # Calculate center and dimensions\n        cx, cy = (x1 + x2) / 2, (y1 + y2) / 2\n        w, h = x2 - x1, y2 - y1\n        \n        # Scale dimensions\n        new_w, new_h = w * scale_factor, h * scale_factor\n        \n        # Expand and clip to image bounds\n        new_x1 = max(0, cx - new_w/2)\n        new_y1 = max(0, cy - new_h/2)\n        new_x2 = min(row[\"width\"], cx + new_w/2)\n        new_y2 = min(row[\"height\"], cy + new_h/2)\n        \n        return [new_x1, new_y1, new_x2, new_y2]\n\n    if len(df_det) > 0:\n        df_rotate['aoi'] = df_det.apply(partial(get_aoi, scale_factor=AOI_FACTOR), axis=1)\n\n        model_dirs = glob.glob(os.path.join(MODEL_DIR, \"det_1024/*\"))\n        cfg = joblib.load(os.path.join(model_dirs[0], \"cfg.pkl\"))\n        print(f\"\\n[Rank {rank}] Starting lead detection V2... len(df_rotate) = {len(df_rotate):_}\")\n\n        df_det = run_det(\n            cfg=cfg,\n            model_dirs=model_dirs,\n            df_infer=df_rotate,\n            mixed_precision=True,\n            distributed=False,\n            local_rank=0, # ALWAYS 0\n            world_size=1,\n            log_folder=LOG_DIR,\n            fold=0,\n            save_masks=True, # Save masks here\n            mask_dir=MASK_DIR\n        )\n        \n    outpath = os.path.join(LOG_DIR, f\"detection_v2_rank{rank}.csv\")\n    df_det.to_csv(outpath, index=False)\n    \n    # Cleanup\n    import gc\n    del df_rotate, df_det, meta_df\n    gc.collect()\n    print(f\"\\n[Rank {rank}] Detection complete. Results saved to {MASK_DIR}.\")\n    dist.barrier(device_ids=[0])\n    dist.destroy_process_group()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:34:18.308498Z","iopub.execute_input":"2026-01-15T20:34:18.308838Z","iopub.status.idle":"2026-01-15T20:34:18.324682Z","shell.execute_reply.started":"2026-01-15T20:34:18.308816Z","shell.execute_reply":"2026-01-15T20:34:18.32396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!export CUDA_VISIBLE_DEVICES=0,1 SUBMISSION=$SUBMISSION && torchrun --nproc_per_node=2 --rdzv_backend=c10d --rdzv_endpoint=localhost:0 _infer_part1.py\nprint(\"Complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:34:18.32529Z","iopub.execute_input":"2026-01-15T20:34:18.325453Z","iopub.status.idle":"2026-01-15T20:35:32.603433Z","shell.execute_reply.started":"2026-01-15T20:34:18.32544Z","shell.execute_reply":"2026-01-15T20:35:32.602552Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Infer Part 2","metadata":{}},{"cell_type":"code","source":"%%writefile _infer_part2.py\n\nimport os\nimport torch\nimport sys\nimport gc\nimport ast\nimport glob\nfrom copy import deepcopy\nfrom types import SimpleNamespace\nfrom importlib import import_module\nfrom functools import partial\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nimport joblib\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.distributed as dist\n\nos.environ['NO_ALBUMENTATIONS_UPDATE'] = '1'\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0,1\"\nprint(f\"CUDA_VISIBLE_DEVICES: {os.environ.get('CUDA_VISIBLE_DEVICES')}\")\nprint(f\"GPU Count Seen by Python: {torch.cuda.device_count()}\")\n\nsys.path.append('/kaggle/input/physionet-2025-submission/')\nsys.path.append('./logs_final/')\n\nfrom src.utils.torch import batch_to_device, count_parameters, crop_2d\nfrom src.models.utils import get_model\nfrom src.augs.albu import get_transfos\n\n\n#########################\n#### Ensemble Models ####\n#########################\n\ndef load_ensemble_models(model_dirs, cfg):\n    models = []\n    for mdir in model_dirs:\n        model_cfg = joblib.load(os.path.join(mdir, \"cfg.pkl\"))\n\n        # Load fullfit first if available\n        mpath = glob.glob(os.path.join(mdir, \"*fullfit*.pt\")) + glob.glob(os.path.join(mdir, \"*.pt\"))\n        model_cfg.load_cfg.wpath = mpath[0]\n        model_cfg.load_cfg.skip_str = None\n        model_cfg.compile = False\n        \n        model = get_model(model_cfg, inference_mode=True)\n        model = model.to(cfg.local_rank).eval()\n        models.append(model)\n        \n        # Clean up cfg\n        del model_cfg\n    \n    return models\n\nclass EnsembleModel(nn.Module):\n    def __init__(self, models, activation=None, method='mean'):\n        super().__init__()\n        self.models = nn.ModuleList(models)\n        self.activation = activation\n        self.method = method\n\n    def forward(self, x):\n        outputs = []\n        for model in self.models:\n            logits = model(x)\n            if self.activation == 'softmax':\n                logits = torch.softmax(logits, dim=-1)\n            elif self.activation == 'sigmoid':\n                logits = torch.sigmoid(logits)\n            outputs.append(logits)\n        \n        stacked = torch.stack(outputs)\n        if self.method == 'mean':\n            return stacked.mean(dim=0)\n        elif self.method == 'median':\n            return stacked.median(dim=0).values\n        elif self.method == 'mode':\n            return stacked.mode(dim=0).values\n        elif self.method == 'max':\n            return stacked.max(dim=0).values\n        else:\n            raise ValueError(f\"Unknown method: {self.method}\")\n\nclass EnsembleModelDET(nn.Module):\n    def __init__(self, models):\n        super().__init__()\n        self.models = nn.ModuleList(models)\n\n    def forward(self, x, height, width, aoi, threshold=0.25):\n        outputs = []\n        outputs_mask = []\n\n        for model in self.models:\n            logits, mask = model(x)\n\n            # Lead boxes\n            logits[:, 4:] = F.sigmoid(logits[:, 4:])\n            logits = model.decode_predictions_argmax(\n                y_pred=logits,\n                height=height,\n                width=width,\n                aoi=aoi,\n            )\n            outputs.append(logits)\n\n            # Lead masks\n            mask = F.sigmoid(mask)\n            outputs_mask.append(mask)\n        \n        # Ensemble\n        outputs = torch.stack(outputs)\n        outputs = outputs.median(dim=0).values\n\n        outputs_mask = torch.stack(outputs_mask)\n        outputs_mask = outputs_mask.mean(dim=0)\n\n        # Convert to labels\n        background_mask = (torch.zeros_like(outputs_mask[:, :1, ...], device=outputs_mask.device) + threshold) # background class\n        outputs_mask = torch.cat([background_mask, outputs_mask], dim=1) # stack\n\n        return outputs, outputs_mask\n\n\n#################\n#### Modules ####\n#################\ndef predict_digi(\n    cfg,\n    model,\n    test_loader,\n    mixed_precision=True,\n    distributed=False,\n    world_size=0,\n    local_rank=0,\n):\n    model.eval()\n    ids = []\n    preds = []\n\n    with torch.no_grad():\n        for batch in tqdm(test_loader, disable=local_rank != 0):\n            with torch.amp.autocast(cfg.device.type, enabled=mixed_precision):\n\n                if distributed:\n                    batch= batch_to_device(batch, device=cfg.local_rank)\n                else:\n                    batch= batch_to_device(batch, device=cfg.device)\n                \n                x = batch[\"input\"].float()\n                nrows = batch[\"number_of_rows\"].int()\n                ids = batch['id']\n                leads = batch['lead']\n\n                # Reshape\n                bs,d,c,h,w = x.shape\n                x = x.view(bs*d,c,h,w)\n\n                # Option 1: TTA\n                if \"ii\" in cfg.dataset:\n                    # TTA: average normal and horizontal flip\n                    y0 = model(x)\n                    y1 = torch.flip(model(torch.flip(x, dims=[-1])), dims=[-1])\n                    y_pred = (y0 + y1) / 2\n                else:\n                    # TTA: 4-way augmentation (normal, h-flip, v-flip, both flips)\n                    y0 = model(x)\n                    y1 = torch.flip(model(torch.flip(x, dims=[-1])), dims=[-1])\n                    y2 = model(torch.flip(x, dims=[-2])) * -1\n                    y3 = torch.flip(model(torch.flip(x, dims=[-1, -2])), dims=[-1]) * -1\n                    y_pred = (y0 + y1 + y2 + y3) / 4\n\n                # # Option 2: No TTA\n                # if \"ii\" in cfg.dataset:\n                #     y_pred = model(x)\n                # else:\n                #     y_pred = model(x)\n\n                # Mean to 0\n                y_pred = y_pred - y_pred.mean(dim=1, keepdim=True)\n\n                # Interpolate to target shape\n                for b in range(bs):\n                    n = nrows[b].item()\n                    \n                    # Interpolate\n                    pred = F.interpolate(\n                        y_pred[b:b+1, None], \n                        size=n, \n                        mode='linear', \n                        align_corners=True,\n                    ).squeeze()\n                    \n                    # Save as NPY\n                    pred = pred.cpu().numpy()\n                    outpath = os.path.join(PRED_DIR, ids[b], f\"{MODEL_RESIZE_STR}_{leads[b]}.npy\")\n                    np.save(outpath, pred)\n\n    preds = np.zeros((1,))\n    \n    if local_rank == 0:\n        return preds\n    else:\n        return 0    \n\ndef run_digi(\n    cfg: SimpleNamespace,\n    model_dirs: list[str],\n    df_infer: pd.DataFrame,\n    mixed_precision=True,\n    distributed=False,\n    local_rank=0,\n    world_size=1,\n    log_folder=None,\n    fold=0,\n): \n    # Reinit logger\n    cfg.fold = fold\n\n    # Dataset class\n    dpath= f\"src.data.{cfg.dataset}\"\n    dataset_class = import_module(dpath).CustomDataset\n\n    # Dataset - Infer\n    transfos_test = get_transfos(augment=False, **vars(cfg.albu_cfg))\n    test_dataset = dataset_class(\n        cfg=cfg,\n        df=df_infer,\n        transforms=transfos_test,\n        train=False,\n        infer=True,\n    )\n\n    # Dataloaders\n    test_loader = torch.utils.data.DataLoader(\n        test_dataset,\n        batch_size=1 if KAGGLE else 4,\n        shuffle=False,\n        num_workers=1 if KAGGLE else 4,\n        pin_memory=True,\n        persistent_workers=False,\n        prefetch_factor=2,\n        collate_fn=test_dataset.collate_fn,\n    )\n\n    # Load model ensemble\n    models = load_ensemble_models(model_dirs, cfg)\n    model = EnsembleModel(models=models, activation=None)\n    model = model.to(cfg.local_rank).eval()\n    n_params = count_parameters(model)\n\n    if cfg.local_rank == 0:\n        print(f\"    -> {len(test_dataset)} test samples\")\n        print(f\"    -> {n_params:_} trainable parameters\\n\")\n\n    # Eval\n    preds = predict_digi(\n        cfg=cfg,\n        model=model,\n        test_loader=test_loader,\n        mixed_precision=mixed_precision,\n        distributed=distributed,\n        world_size=world_size,\n        local_rank=local_rank,\n    )\n\n    return preds\n\nif __name__ == \"__main__\":\n    \n    ############################\n    ##### Distributed Init #####\n    ############################\n    if \"LOCAL_RANK\" in os.environ:\n        local_rank = int(os.environ[\"LOCAL_RANK\"])\n        rank = int(os.environ[\"RANK\"])\n        world_size = int(os.environ[\"WORLD_SIZE\"])\n        \n        # Force strict isolation so \"cuda:0\" always maps to the local GPU\n        os.environ[\"CUDA_VISIBLE_DEVICES\"] = str(local_rank)\n        \n        dist.init_process_group(backend=\"nccl\")\n        torch.cuda.set_device(0)\n        print(f\"[Init] Rank {rank}/{world_size} initialized on isolated GPU {local_rank}\")\n    else:\n        rank = 0\n        local_rank = 0\n        world_size = 1\n        print(\"Running in standard single-process mode.\")\n\n    ##################\n    ##### Params #####\n    ##################\n    CLASSES = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'II_full']\n    CLASSES2IDX = {v:k+1 for k,v in enumerate(CLASSES)} \n    IDX2CLASSES = {v:k for k,v in CLASSES2IDX.items()}\n\n    KAGGLE = 'KAGGLE_URL_BASE' in os.environ\n    SUBMISSION = os.environ['SUBMISSION'] == \"True\"\n    MODEL_DATASET = os.environ.get(\"MODEL_DATASET\", None)\n    MODEL_RESIZE = eval(os.environ.get(\"MODEL_RESIZE\", \"(1,2,3)\"))\n    MODEL_RESIZE_STR = \"_\".join(map(str, MODEL_RESIZE)) if MODEL_RESIZE else None\n\n    WSCALE = 1.05\n    HSCALE = 1.3\n    WSCALE_II_FULL = 1.05\n    HSCALE_II_FULL = 2.15\n    AOI_FACTOR = 2.25\n\n    if rank == 0:\n        print(f\"[PARAMS] SUBMISSION={SUBMISSION}, KAGGLE={KAGGLE}\")\n        print(f\"[SCALES] WSCALE={WSCALE}, HSCALE={HSCALE}, WSCALE_II_FULL={WSCALE_II_FULL}, HSCALE_II_FULL={HSCALE_II_FULL}, AOI_FACTOR={AOI_FACTOR}\")\n        print(f\"[OTHER] MODEL_DATASET={MODEL_DATASET}, MODEL_RESIZE={MODEL_RESIZE}\")\n        \n    if KAGGLE:\n        MODEL_DIR = \"/kaggle/input/physionet-2025-submission/models/\"\n        LOG_DIR = \"/kaggle/working/\"\n        MASK_DIR = \"/kaggle/working/masks/\"\n        PRED_DIR = \"/kaggle/working/preds/\"\n\n        if SUBMISSION:\n            DATA_DIR = \"/kaggle/input/physionet-ecg-image-digitization/test/\"\n            META_PATH = \"/kaggle/input/physionet-ecg-image-digitization/test.csv\"\n        else:\n            DATA_DIR = \"/kaggle/input/physionet-ecg-image-digitization/train/\"\n            META_PATH = \"/kaggle/input/physionet-2025-submission/train.csv\"\n            LABEL_PATH = \"/kaggle/input/physionet-2025-submission/labels.csv\"\n            FOLDS_PATH = \"/kaggle/input/physionet-2025-submission/folds.csv\"\n    else:\n        MODEL_DIR = \"./logs_final/models/\"\n        LOG_DIR = \"./logs_infer/tmp/\"\n        MASK_DIR = \"./logs_infer/tmp/masks/\"\n        PRED_DIR = \"./logs_infer/tmp/preds/\"\n\n        if SUBMISSION:\n            DATA_DIR = \"./data/raw/test/\"\n            META_PATH = \"./data/raw/test.csv\"\n        else:\n            DATA_DIR = \"./data/raw/train/\"\n            META_PATH = \"./logs_final/train.csv\"\n            LABEL_PATH = \"./logs_final/labels.csv\"\n            FOLDS_PATH = \"./logs_final/folds.csv\"\n    \n    os.makedirs(LOG_DIR, exist_ok=True)\n    os.makedirs(MASK_DIR, exist_ok=True)\n    os.makedirs(PRED_DIR, exist_ok=True)\n    \n    \n    #########################\n    ##### Digitization ######\n    #########################\n\n    # Load detection CSV\n    outpath = os.path.join(LOG_DIR, f\"detection_v2_rank{rank}.csv\")\n    df_det = pd.read_csv(outpath)\n    df_det[\"meta\"] = df_det[\"meta\"].apply(eval)\n    print(df_det.head())\n\n    # Make output dir for preds\n    for idx in df_det[\"id\"].values:\n        outpath = os.path.join(PRED_DIR, str(idx))\n        os.makedirs(outpath, exist_ok=True)\n\n    # Load models\n    model_dirs = []\n    _arr = sorted(glob.glob(os.path.join(MODEL_DIR, \"digi_ft/*/\")))\n    for mdir in _arr:\n        cfg = joblib.load(os.path.join(mdir, \"cfg.pkl\"))\n\n        # Filter by dataset and resize\n        if cfg.dataset == MODEL_DATASET and cfg.albu_cfg.resize == MODEL_RESIZE:\n            model_dirs.append(mdir)\n\n    # Reload valid CFG\n    cfg = joblib.load(os.path.join(model_dirs[0], \"cfg.pkl\"))\n\n    print(\"\\nStarting digitization...\")\n    print(f\"len(df_det) = {len(df_det):_}\")\n    print(f\"len(model_dirs) = {len(model_dirs):_}\")\n\n    # Fpath and offset\n    if SUBMISSION:\n        df_det[\"img_fpath\"] = df_det.apply(lambda row: os.path.join(MASK_DIR, f\"{row['id']}_{row['img_type']:04d}_{'{}'}.npy\"), axis=1)\n    else:\n        df_det[\"img_fpath\"] = df_det.apply(lambda row: os.path.join(MASK_DIR, f\"{row['id']}_{row['img_type']:04d}_{'{}'}.npy\"), axis=1)\n\n    df_det[\"offset_x\"] = -1\n    df_det[\"offset_y\"] = -1\n    \n    # W/H scales\n    if not cfg.dataset.endswith(\"_ii\"):\n        df_det[\"wscale\"] = WSCALE\n        df_det[\"hscale\"] = HSCALE\n    else:\n        df_det[\"wscale\"] = WSCALE_II_FULL\n        df_det[\"hscale\"] = HSCALE_II_FULL\n\n    preds = run_digi(\n        cfg=cfg,\n        model_dirs=model_dirs,\n        df_infer=df_det,\n        mixed_precision=True,\n        distributed=False,\n        local_rank=0,\n        world_size=1,\n        log_folder=LOG_DIR,\n        fold=0,\n    )\n\n    # Cleanup\n    import gc\n    del df_det\n    gc.collect()\n    print(f\"\\n[Rank {rank}] Digitization complete. Results saved to {PRED_DIR}.\")\n    dist.barrier(device_ids=[0])\n    dist.destroy_process_group()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:35:32.605368Z","iopub.execute_input":"2026-01-15T20:35:32.605714Z","iopub.status.idle":"2026-01-15T20:35:32.617161Z","shell.execute_reply.started":"2026-01-15T20:35:32.605691Z","shell.execute_reply":"2026-01-15T20:35:32.616401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob \nimport joblib\n\nseen = set()\nmodel_dirs = sorted(glob.glob(os.path.join(MODEL_DIR, \"digi_ft/*/\")))[::-1]\n\nfor mdir in model_dirs:\n    cfg = joblib.load(os.path.join(mdir, \"cfg.pkl\"))\n\n    # Get OFFSET\n    key= (cfg.dataset, cfg.albu_cfg.resize)\n    if key in seen:\n        continue\n    else:\n        seen.add(key)\n\n    MODEL_DATASET = cfg.dataset \n    MODEL_RESIZE = cfg.albu_cfg.resize\n\n    !CUDA_VISIBLE_DEVICES=0,1 SUBMISSION=$SUBMISSION MODEL_DATASET=$MODEL_DATASET MODEL_RESIZE=$MODEL_RESIZE torchrun --nproc_per_node=2 --rdzv_backend=c10d --rdzv_endpoint=localhost:0 _infer_part2.py\n\nprint(\"Complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:35:32.618085Z","iopub.execute_input":"2026-01-15T20:35:32.618367Z","iopub.status.idle":"2026-01-15T20:37:17.283774Z","shell.execute_reply.started":"2026-01-15T20:35:32.618333Z","shell.execute_reply":"2026-01-15T20:37:17.282862Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Combine predictions\n","metadata":{}},{"cell_type":"code","source":"import os \nimport glob\nimport shutil\n\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\n\nif KAGGLE:\n    MASK_DIR= \"/kaggle/working/masks/\"\n    PRED_DIR= \"/kaggle/working/preds/\"\n    OUTPATH = \"/kaggle/working/submission_bartley.csv\"\nelse:\n    MASK_DIR = \"./logs_infer/tmp/masks/\"\n    PRED_DIR = \"./logs_infer/tmp/preds/\"\n    OUTPATH = \"./logs_infer/tmp/submission.csv\"\n\ndef process_preds(in_dir: str):\n\n    with open(OUTPATH, \"w+\") as outf:\n        \n        # Add columns\n        print(\"id,value\", file=outf)\n\n        # Load metadata\n        leads = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']\n        idxs = os.listdir(in_dir)\n\n        # Iterate all ids\n        for idx in tqdm(idxs):\n            cpreds = {_: {\"pred\": None, \"count\": 0} for _ in leads}\n\n            cdir = os.path.join(in_dir, str(idx)) \n            fpaths = glob.glob(os.path.join(cdir, \"*.npy\"))\n\n            # Ensemble\n            for f in fpaths:\n                lead = os.path.basename(f).removesuffix(\".npy\").split(\"_\")[-1]\n                pred = np.load(f)\n\n                # Add to dict\n                if cpreds[lead][\"count\"] == 0:\n                    cpreds[lead][\"pred\"] = pred\n                else:\n                    cpreds[lead][\"pred\"] += pred\n                cpreds[lead][\"count\"] += 1\n\n            # Average\n            for lead in leads:\n                cpreds[lead][\"pred\"] /= cpreds[lead][\"count\"]\n\n            # Write to submission csv\n            for lead in leads:\n                for j, val in enumerate(cpreds[lead][\"pred\"]):\n                    row_idx = f\"{idx}_{j}_{lead}\"\n                    print(f\"{row_idx},{val}\", file=outf)\n\n    return\n\nif __name__ == \"__main__\":\n    print(f\"Cleaning up masks. MASK_DIR={MASK_DIR}\")\n    if os.path.exists(MASK_DIR):\n        shutil.rmtree(MASK_DIR)\n    print(f\"PRED_DIR={PRED_DIR}, OUTPATH={OUTPATH}\")\n    process_preds(in_dir=PRED_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:17.28502Z","iopub.execute_input":"2026-01-15T20:37:17.28597Z","iopub.status.idle":"2026-01-15T20:37:17.694145Z","shell.execute_reply.started":"2026-01-15T20:37:17.285943Z","shell.execute_reply":"2026-01-15T20:37:17.693369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if KAGGLE:\n    !wc -l /kaggle/working/submission_bartley.csv\nelse:\n    !wc -l ./logs_infer/tmp/submission.csv\n    !du -h ./logs_infer/ | tail -n 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:17.694997Z","iopub.execute_input":"2026-01-15T20:37:17.695294Z","iopub.status.idle":"2026-01-15T20:37:17.828005Z","shell.execute_reply.started":"2026-01-15T20:37:17.695276Z","shell.execute_reply":"2026-01-15T20:37:17.82708Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Scoring","metadata":{}},{"cell_type":"code","source":"import pandas as pd \nimport numpy as np\nimport glob \n\nif not SUBMISSION:\n\n    if KAGGLE:\n        LABEL_PATH = \"/kaggle/input/physionet-2025-submission/labels.csv\"\n        df_digi = pd.read_csv(\"/kaggle/working/submission_bartley.csv\")\n    else:\n        LABEL_PATH = \"./data/processed/labels.csv\"\n        df_digi = pd.read_csv(\"./logs_infer/tmp/submission.csv\")\n\n\n    print(\"\\nStarting scoring...\")\n    from src.metrics.comp import score\n\n    sol = pd.read_csv(LABEL_PATH)\n\n    print(f\"len(sub) = {len(df_digi):_}, len(sol) = {len(sol):_}\")\n    sol = sol[sol[\"id\"].isin(df_digi[\"id\"].values)]\n    print(f\"len(sub) = {len(df_digi):_}, len(sol) = {len(sol):_}\\n\")\n\n\n    _score, score_df = score(\n        solution = sol.copy(), \n        submission = df_digi.copy(), \n        row_id_column_name = \"id\",\n        )\n    cv = _score\n\n    # ===== lead =====\n    print()\n    for lead in ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']:\n        _sub = df_digi[df_digi['id'].str.endswith(f\"_{lead}\")].copy()\n        _sol = sol[sol['id'].str.endswith(f\"_{lead}\")].copy()\n\n        _score, score_df = score(\n            solution = sol.copy(), \n            submission = _sub.copy(), \n            row_id_column_name = \"id\",\n            )\n        print(f\" -> lead: {lead}, score: {_score:.4f}\")\n    print(f\"\\nOverall: {cv:.4f}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:17.829241Z","iopub.execute_input":"2026-01-15T20:37:17.829553Z","iopub.status.idle":"2026-01-15T20:37:17.837741Z","shell.execute_reply.started":"2026-01-15T20:37:17.829512Z","shell.execute_reply":"2026-01-15T20:37:17.836961Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Cleanup","metadata":{}},{"cell_type":"code","source":"import os\nimport shutil\n\ndef clean_working(directory_path: str = \"/kaggle/working/\"):\n    \"\"\"\n    Clean kaggle output directory.\n    \"\"\"\n    if os.path.exists(directory_path):\n        for item in os.listdir(directory_path):\n            if item == \"submission_bartley.csv\" or item == \"detection_v2_rank0.csv\" or item == \"detection_v2_rank1.csv\":\n                continue\n            item_path = os.path.join(directory_path, item)\n            os.remove(item_path) if os.path.isfile(item_path) else shutil.rmtree(item_path)\n        print(f\"All items in '{directory_path}' have been removed.\")\n    else:\n        print(f\"'{directory_path}' does not exist.\")\n\nif KAGGLE:\n    clean_working()\nelse:\n    pass\n\nprint(\"done\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:17.838578Z","iopub.execute_input":"2026-01-15T20:37:17.838978Z","iopub.status.idle":"2026-01-15T20:37:17.850638Z","shell.execute_reply.started":"2026-01-15T20:37:17.838961Z","shell.execute_reply":"2026-01-15T20:37:17.849839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport glob\nimport os\n\n# Prepare format for Harshit\nfiles = glob.glob(\"/kaggle/working/detection_v2_rank*.csv\")\nif files:\n    df = pd.concat([pd.read_csv(f) for f in files], axis=0)\n    !rm -rf /kaggle/working/masks/\n    !rm -rf /kaggle/working/preds/\n    df.to_csv('/kaggle/working/detection.csv', index=False)\n    print(f\"Merged detection csv's with {len(df)} rows.\")\nelse:\n    print(\"No detection csvs found!\")\n\ndel df\nimport gc\ngc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:17.853042Z","iopub.execute_input":"2026-01-15T20:37:17.853262Z","iopub.status.idle":"2026-01-15T20:37:18.237819Z","shell.execute_reply.started":"2026-01-15T20:37:17.853248Z","shell.execute_reply":"2026-01-15T20:37:18.237006Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Harshit Part","metadata":{}},{"cell_type":"code","source":"%%writefile sheoran_script.py\n\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom glob import glob\nimport os, sys, copy, time\nimport cv2\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport torch\nfrom torch import nn\nimport torch.distributed as dist\nimport ast\n#sys.path.insert(0, \"/kaggle/input/timm-1-0-22/timm-1.0.22/\")\nimport timm\n#import segmentation_models_pytorch as smptimm.__version__\n\n# -------------------------------------------------------------------------\n# MODEL DEFINITIONS\n# -------------------------------------------------------------------------\n\nclass OODModel(nn.Module):\n    def __init__(self, CFG):\n        super(OODModel, self).__init__()\n        \n        self.encoder = timm.create_model(CFG.model_name, pretrained=False, global_pool='', num_classes=0)\n        \n        n_feats = self.encoder.num_features\n        \n        self.avgpool = nn.AdaptiveAvgPool2d(1)\n        self.bn = nn.BatchNorm1d(n_feats)\n        \n    def forward(self, inp):\n        inp = torch.nan_to_num(inp, 0, 0, 0)\n        \n        features = self.encoder(inp)\n        \n        if len(features.shape)==4:\n            features = self.avgpool(features).flatten(1,3)\n        if len(features.shape)==3:\n            features = features.mean(1)\n        \n        features = self.bn(features)\n        \n        #logits = self.sigmoid(logits)\n        \n        return features, None\n\ndef predict_ood(CFG, image, device='cuda'):\n    image = torch.as_tensor(image).permute(2,0,1).unsqueeze(0)\n\n    image = torch.as_tensor((image - image.min()) / (image.max() - image.min())).float()\n    \n    image = nn.functional.interpolate(image, CFG.image_size, mode='bilinear')\n    image = image.to(device)\n    \n    outputs = []\n    for model in CFG.models:\n        with torch.no_grad():\n            with torch.cuda.amp.autocast(enabled=True):\n                output = model(image)[0]\n                output = output.float().detach().cpu().numpy()\n                outputs.append(output)\n    output = np.stack(outputs).mean(0)\n\n    return output\n\ndef reconstruct_tensor(CFG, output_tensor, n_cls_token=1, patch_size=14):\n    bs = output_tensor.shape[0]\n    # 1. Remove CLS token (first token)\n    if n_cls_token:\n        patches = output_tensor[:, n_cls_token:, :]  # Shape [1, 625, 196]\n    else:\n        patches = output_tensor\n        \n    # 2. Reshape to [1, 25, 25, 14, 14] (25x25 grid of 14x14 patches)\n    patches = patches.view(bs, CFG.image_size[0]//patch_size, CFG.image_size[1]//patch_size, patch_size, patch_size*2)\n\n    # 3. Re-arrange patches into spatial order\n    #    - Combine grid rows first, then patch rows\n    reconstructed = patches.permute(0, 1, 3, 2, 4).contiguous()\n    reconstructed = reconstructed.view(bs, CFG.image_size[0], CFG.image_size[1]*2)  # Final shape\n\n    return reconstructed\n    \nclass DigitizationModel(nn.Module):\n    def __init__(self, CFG):\n        super(DigitizationModel, self).__init__()\n\n        self.CFG = CFG\n        \n        self.encoder = timm.create_model(CFG.model_name, pretrained=False, global_pool='', num_classes=0, img_size=CFG.image_size)\n        \n        self.n_feats = self.encoder.num_features\n        self.patch_size = 16\n        \n        self.head = nn.Linear(self.n_feats, self.patch_size**2*2)\n        \n    def forward(self, inp):\n        inp = torch.nan_to_num(inp, 0, 0, 0)\n        \n        features = self.encoder(inp)\n        \n        features = self.head(features)\n        \n        masks = reconstruct_tensor(self.CFG, features, n_cls_token=5, patch_size=self.patch_size)\n        \n        logits = masks.mean(1)\n\n        logits = torch.nan_to_num(logits, 0, 0, 0)\n        \n        return logits, None\n\ndef predict_digitization(CFG, models, images, device='cuda'):\n    images_ = []\n    for image in images:\n        image = torch.as_tensor(image).permute(2,0,1).unsqueeze(0)\n    \n        image = torch.as_tensor((image - image.min()) / (image.max() - image.min())).float()\n        \n        image = nn.functional.interpolate(image, CFG.image_size, mode='bilinear')\n        images_.append(image[0])\n        \n    image = torch.stack(images_)\n    image = image.to(device)\n    \n    outputs = []\n    for model in models:\n        with torch.no_grad():\n            with torch.cuda.amp.autocast(enabled=False, dtype=torch.float32):\n                output = model(image)[0]\n                output = output.float().detach().cpu().numpy()\n                outputs.append(output)\n    output = np.stack(outputs).mean(0)\n\n    return output\n\nclass CFG:\n    class ood_detector:\n        model_name = \"tf_efficientnetv2_s.in21k_ft_in1k\"\n        image_size = [1024, 1024]\n    \n    class digitization_model:\n        model_name = \"vit_small_patch16_dinov3.lvd1689m\"\n        image_size = [336, 896*4]\n\ndef crop_box_xyxyn(image, box, hpad=0.05, wpad=0.05):\n    x1, y1, x2, y2 = box\n    xw, yh = x2-x1, y2-y1\n    x1, y1, x2, y2 = x1-(xw*wpad), y1-(yh*hpad), x2+(xw*wpad), y2+(yh*hpad)\n    \n    height, width = image.shape[:2]\n    x1, y1, x2, y2 = int(np.clip(x1*width, 0, width)), int(np.clip(y1*height, 0, height)), int(np.clip(x2*width, 0, width)), int(np.clip(y2*height, 0, height))\n    crop = image[y1:y2, x1:x2]\n    return crop\n\ndef crop_box_xyxy(image, box, hpad=0.05, wpad=0.05):\n    x1, y1, x2, y2 = box\n    xw, yh = x2-x1, y2-y1\n    x1, y1, x2, y2 = x1-(xw*wpad), y1-(yh*hpad), x2+(xw*wpad), y2+(yh*hpad)\n    \n    height, width = image.shape[:2]\n    x1, y1, x2, y2 = int(np.clip(x1, 0, width)), int(np.clip(y1, 0, height)), int(np.clip(x2, 0, width)), int(np.clip(y2, 0, height))\n    crop = image[y1:y2, x1:x2]\n    return crop\n\n# -------------------------------------------------------------------------\n# MAIN EXECUTION\n# -------------------------------------------------------------------------\n\nif __name__ == \"__main__\":\n\n    # --- DISTRIBUTED INIT ---\n    if \"LOCAL_RANK\" in os.environ:\n        local_rank = int(os.environ[\"LOCAL_RANK\"])\n        rank = int(os.environ[\"RANK\"])\n        world_size = int(os.environ[\"WORLD_SIZE\"])\n        \n        # Force strict isolation\n        os.environ[\"CUDA_VISIBLE_DEVICES\"] = str(local_rank)\n        \n        dist.init_process_group(backend=\"nccl\")\n        torch.cuda.set_device(0)\n        device = torch.device(\"cuda:0\")\n        print(f\"[Init] Rank {rank}/{world_size} initialized on isolated GPU {local_rank}\")\n    else:\n        rank = 0\n        local_rank = 0\n        world_size = 1\n        device = torch.device(\"cuda\")\n        print(\"Running in standard single-process mode.\")\n\n    # --- LOAD MODELS ---\n    \n    # OOD Model\n    model = OODModel(CFG.ood_detector)\n    st = torch.load(f\"/kaggle/input/try3cls-tf-efficientnetv2-s-in21k-ft-in1k-v2/0_best.pth\", map_location='cpu')\n    print(model.load_state_dict(st, strict=False))\n    model.eval()\n    model = model.to(device)\n    CFG.ood_detector.models = [model]\n\n    # Digitization Models\n    models = []\n    #'''\n    for F in range(1):\n        model = DigitizationModel(CFG.digitization_model)\n        st = torch.load(f\"/kaggle/input/try3seg-vit-small-patch16-dinov3-v27/{F}_best.pth\", map_location='cpu')\n        print(model.load_state_dict(st))\n        model.eval()\n        model = model.to(device)\n        models.append(copy.deepcopy(model))\n    #'''\n\n    #'''\n    for F in range(1):\n        model = DigitizationModel(CFG.digitization_model)\n        st = torch.load(f\"/kaggle/input/try3seg-vit-small-patch16-dinov3-v27-snr/{F}_best.pth\", map_location='cpu')\n        print(model.load_state_dict(st))\n        model.eval()\n        model = model.to(device)\n        models.append(copy.deepcopy(model))\n    #'''\n\n    #'''\n    for F in range(1):\n        model = DigitizationModel(CFG.digitization_model)\n        st = torch.load(f\"/kaggle/input/try10seg-vit-small-patch16-dinov3-v6/{F}_best.pth\", map_location='cpu')\n        print(model.load_state_dict(st))\n        model.eval()\n        model = model.to(device)\n        models.append(copy.deepcopy(model))\n    #'''\n\n    '''\n    for F in range(1):\n        model = DigitizationModel(CFG.digitization_model)\n        st = torch.load(f\"/kaggle/input/try8seg-vit-small-patch16-dinov3-v5/{F}_best.pth\", map_location='cpu')\n        print(model.load_state_dict(st))\n        model.eval()\n        model = model.to(device)\n        models.append(copy.deepcopy(model))\n    #'''\n    \n    print(len(models))\n    CFG.digitization_model.models = models\n\n    # --- PREPARE DATA ---\n\n    test = pd.read_csv(\"/kaggle/input/physionet-ecg-image-digitization/test.csv\")\n    test['idlead'] = test.id.astype(str)+\"_\"+test.lead\n    lead_names = [\"I\", \"IIS\", \"III\", \"aVR\", \"aVL\", \"aVF\", \"V1\", \"V2\", \"V3\", \"V4\", \"V5\", \"V6\", \"II\"]\n    idlead_to_nrows = dict(zip(test.idlead, test.number_of_rows))\n\n    df_det = pd.read_csv('/kaggle/working/detection.csv')\n    \n    # Safely evaluate meta string to dict\n    def safe_eval(x):\n        try: return ast.literal_eval(x)\n        except: return []\n        \n    # Assuming meta is stringified list of dicts from previous step\n    if isinstance(df_det.meta.iloc[0], str):\n        df_det['meta'] = df_det['meta'].apply(safe_eval)\n\n    id_to_det_meta = dict(zip(df_det.id, df_det.meta))\n    id_to_rot90 = dict(zip(df_det.id, df_det.rot90))\n\n    prototype_features = np.load(f\"/kaggle/input/try3cls-tf-efficientnetv2-s-in21k-ft-in1k-v2/prototype_features_F0.npy\")\n    # prototype_features.shape\n\n    # Initialize submission file (Rank specific to avoid collision)\n    submission_path = f'/kaggle/working/submission_sheoran_rank{rank}.csv'\n    pd.DataFrame(columns=['id', 'value']).to_csv(submission_path, index=False)\n\n    test_ids = test.id.unique()\n    \n    # Split data among ranks\n    if world_size > 1:\n        my_ids = np.array_split(test_ids, world_size)[rank]\n        print(f\"[Rank {rank}] Processing {len(my_ids)} of {len(test_ids)} total IDs\")\n    else:\n        my_ids = test_ids\n\n    # --- INFERENCE LOOP ---\n    \n    for iid in tqdm(my_ids, disable=(rank!=0)):\n        \n        #if 1:\n        try:\n            \n            file = f\"/kaggle/input/physionet-ecg-image-digitization/test/{iid}.png\"\n            \n            image = cv2.imread(file)[:, :, ::-1]\n\n            det_meta = id_to_det_meta[iid]\n            \n            nt_rot = -1 * id_to_rot90[iid]\n            \n            image = np.rot90(image, k=nt_rot)\n\n            features = predict_ood(CFG.ood_detector, image.copy(), device=device)[0]\n\n            sims = nn.functional.cosine_similarity(torch.as_tensor(features), torch.as_tensor(prototype_features), dim=-1).numpy()\n        \n            if sims.max() < 0.5:\n                continue\n            \n            lead_images, lead_names = [], []\n            for det in det_meta:\n                lead_box = det['box']\n                nm = det['cls']\n                if nm=='II': continue\n                lead_image = crop_box_xyxy(image.copy(), lead_box, hpad=0.175, wpad=0.025)\n                lead_images.append(lead_image.copy())\n                lead_names.append(nm)\n\n            #'''\n            predicted_signals1 = predict_digitization(CFG.digitization_model,  CFG.digitization_model.models[:], lead_images[:4], device=device)\n            predicted_signals2 = predict_digitization(CFG.digitization_model,  CFG.digitization_model.models[:], lead_images[4:8], device=device)\n            predicted_signals3 = predict_digitization(CFG.digitization_model,  CFG.digitization_model.models[:], lead_images[8:], device=device)\n\n            pred_sig = np.concatenate([predicted_signals1, predicted_signals2, predicted_signals3], 0)\n\n            lead_images_flip = [cv2.flip(img, 1) for img in lead_images]\n\n            predicted_signals1 = predict_digitization(CFG.digitization_model, CFG.digitization_model.models[:], lead_images_flip[:4], device=device)\n            predicted_signals2 = predict_digitization(CFG.digitization_model, CFG.digitization_model.models[:], lead_images_flip[4:8], device=device)\n            predicted_signals3 = predict_digitization(CFG.digitization_model, CFG.digitization_model.models[:], lead_images_flip[8:], device=device)\n            pred_sig_flip = np.concatenate([predicted_signals1, predicted_signals2, predicted_signals3], 0)\n            pred_sig_flip = np.flip(pred_sig_flip, axis=-1)\n\n            predicted_signals = (pred_sig + pred_sig_flip) / 2.0\n            \n            #'''\n            \n            '''\n            predicted_signals = []\n            for li in range(len(lead_images)):\n                predicted_signal = predict_digitization(CFG.digitization_model, lead_images[li:li+1], device=device)\n                predicted_signals.extend(predicted_signal)\n            predicted_signals = np.stack(predicted_signals)\n            '''\n\n            #predicted_signals = predict_digitization(CFG.digitization_model, lead_images[:], device=device)\n            \n        except Exception as e:\n            # print(f\"Error processing {iid}: {e}\")\n            #lead_names = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1',  'V2', 'V3', 'V4', 'V5', 'V6', 'II_full']\n            predicted_signals = np.zeros((12, 2016), dtype=np.float32)\n            \n        for lead_name, signal in zip(lead_names, predicted_signals):\n            if lead_name=='II': continue #skips the shorter II\n\n            if lead_name=='II_full': lead_name = 'II'\n            \n            key = f\"{iid}_{lead_name}\"\n            if key in idlead_to_nrows:\n                nrows = idlead_to_nrows[key]\n            else:\n                nrows = 2500 # Fallback\n\n            signal = nn.functional.interpolate(torch.as_tensor(signal).unsqueeze(0).unsqueeze(0), (nrows,), mode='linear')[0][0].numpy()\n\n            sub = {'id': [], 'value': []}\n            for i in range(signal.shape[0]):\n                sub['id'].append(f\"{iid}_{i}_{lead_name}\")\n                sub['value'].append(signal[i])\n\n            pd.DataFrame(sub).to_csv(submission_path, mode='a', header=False, index=False)\n            \n            #break\n\n        #except:\n            #pass\n        \n        #break\n    \n    if world_size > 1:\n        dist.barrier()\n        print(f\"[Rank {rank}] Finished.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:18.238823Z","iopub.execute_input":"2026-01-15T20:37:18.239079Z","iopub.status.idle":"2026-01-15T20:37:18.248785Z","shell.execute_reply.started":"2026-01-15T20:37:18.239059Z","shell.execute_reply":"2026-01-15T20:37:18.247933Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!export CUDA_VISIBLE_DEVICES=0,1 && torchrun --nproc_per_node=2 --rdzv_backend=c10d --rdzv_endpoint=localhost:0 sheoran_script.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-15T20:37:18.249582Z","iopub.execute_input":"2026-01-15T20:37:18.24982Z","iopub.status.idle":"2026-01-15T20:37:55.904562Z","shell.execute_reply.started":"2026-01-15T20:37:18.249798Z","shell.execute_reply":"2026-01-15T20:37:55.903582Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Ensemble","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport glob\nimport os\n\n# 1. Load Harshit (Partial predictions)\nfiles = glob.glob(\"/kaggle/working/submission_sheoran_rank*.csv\")\ndf_harshit = pd.concat([pd.read_csv(f) for f in files], axis=0)\ndf_harshit = df_harshit.sort_values(\"id\").reset_index(drop=True)\n\n# 2. Load Bartley (Base/Complete predictions)\ndf_bartley = pd.read_csv(\"/kaggle/working/submission_bartley.csv\")\n\nprint(f\"Partial (Harshit): {df_harshit.shape}, Base (Bartley): {df_bartley.shape}\")\n\n# 3. Merge: Left Join on Bartley to ensure we have all rows\n# We use suffixes to distinguish the value columns\ndf_merge = df_bartley.merge(df_harshit, on=\"id\", how=\"left\", suffixes=('_bartley', '_harshit'))\n\n# 4. Ensemble Logic\n# .mean(axis=1) automatically ignores NaNs. \n# If 'value_harshit' is NaN, it returns 'value_bartley'. \n# If both exist, it returns (value_bartley + value_harshit) / 2.\ndf_merge[\"value\"] = df_merge[[\"value_bartley\", \"value_harshit\"]].mean(axis=1)\n\n# 5. Format Final Dataframe\ndf_final = df_merge[[\"id\", \"value\"]].sort_values(\"id\").reset_index(drop=True)\n\n# 6. Delete files (Executes shell command)\nos.system(\"rm -rf /kaggle/working/*\")\n\n# 7. Save\ndf_final.to_csv('/kaggle/working/submission.csv', index=False)\nprint(f\"Local minimum escaped (hopefully). Final shape: {df_final.shape}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}