{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":117682,"databundleVersionId":15062069},{"sourceType":"datasetVersion","sourceId":14776787,"datasetId":9445668,"databundleVersionId":15629475},{"sourceType":"modelInstanceVersion","sourceId":769548,"databundleVersionId":15877044,"modelInstanceId":587868,"modelId":599446},{"sourceType":"modelInstanceVersion","sourceId":745576,"databundleVersionId":15629445,"modelInstanceId":569214,"modelId":580733},{"sourceType":"modelInstanceVersion","sourceId":744619,"databundleVersionId":15618054,"modelInstanceId":568400,"modelId":580733},{"sourceType":"kernelVersion","sourceId":296543947},{"sourceType":"kernelVersion","sourceId":296804121}],"dockerImageVersionId":31260,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"THIS is draft ..... And a test ... No topo post processing ","metadata":{}},{"cell_type":"markdown","source":"The \"Sandwich\" Concept\n\nIf you are trying to find ink on slice 100 and Z_CONTEXT = 2, the model doesn't just look at slice 100. It looks at a 5-layer \"sandwich\":\n\nSlice 98\n\nSlice 99\n\nSlice 100 (Target)\n\nSlice 101\n\nSlice 102\n\n2. Why is it used? (2.5D vs. 2D)\n\n2D Approach (Z_CONTEXT = 0): The model sees only 1 slice. It is hard to distinguish ink from charred papyrus because they look very similar on a single plane.\n\n2.5D Approach (Z_CONTEXT > 0): By providing layers above and below, the model can see the 3D structure of the ink. Real ink has a specific \"height\" and \"depth\" profile within the papyrus fibers that a single slice can't capture.","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"single_model=False\nTTA=True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.031390Z","iopub.execute_input":"2026-03-02T15:40:24.031660Z","iopub.status.idle":"2026-03-02T15:40:24.037938Z","shell.execute_reply.started":"2026-03-02T15:40:24.031614Z","shell.execute_reply":"2026-03-02T15:40:24.037395Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ARTEFACTS","metadata":{}},{"cell_type":"code","source":"if single_model: \n    import os\n    import zipfile\n    import numpy as np\n    import torch\n    import torch.nn as nn\n    from pathlib import Path\n    from PIL import Image, ImageSequence\n    from tqdm import tqdm\n    from collections import OrderedDict\n    import tifffile\n    \n    # ==========================================\n    # 1. CONFIG\n    # ==========================================\n    Z_CONTEXT = 3          # 7 slices\n    THRESHOLD = 0.15\n    MIN_CC_SIZE = 100\n    \n    DATA_PATH = Path(\"/kaggle/input/vesuvius-challenge-surface-detection\")\n    TEST_IMG_DIR = DATA_PATH / \"test_images\"\n    CHECKPOINT_PATH = Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/model_epoch_150.pth\")\n    \n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    \n    # ==========================================\n    # 2. MODEL\n    # ==========================================\n    class Pix2PixGenerator(nn.Module):\n        def __init__(self, in_ch=7, out_ch=1):\n            super().__init__()\n            self.e1 = nn.Sequential(\n                nn.Conv2d(in_ch, 64, 4, 2, 1),\n                nn.LeakyReLU(0.2, inplace=True)\n            )\n            self.e2 = nn.Sequential(\n                nn.Conv2d(64, 128, 4, 2, 1),\n                nn.BatchNorm2d(128),\n                nn.LeakyReLU(0.2, inplace=True)\n            )\n            self.e3 = nn.Sequential(\n                nn.Conv2d(128, 256, 4, 2, 1),\n                nn.BatchNorm2d(256),\n                nn.LeakyReLU(0.2, inplace=True)\n            )\n    \n            self.d1 = nn.Sequential(\n                nn.ConvTranspose2d(256, 128, 4, 2, 1),\n                nn.BatchNorm2d(128),\n                nn.ReLU(inplace=True)\n            )\n            self.d2 = nn.Sequential(\n                nn.ConvTranspose2d(256, 64, 4, 2, 1),\n                nn.BatchNorm2d(64),\n                nn.ReLU(inplace=True)\n            )\n            self.final = nn.ConvTranspose2d(128, out_ch, 4, 2, 1)\n    \n        def forward(self, x):\n            en1 = self.e1(x)\n            en2 = self.e2(en1)\n            en3 = self.e3(en2)\n            de1 = self.d1(en3)\n            de2 = self.d2(torch.cat([de1, en2], 1))\n            return self.final(torch.cat([de2, en1], 1))\n    \n    \n    def load_generator(path: Path, device):\n        model = Pix2PixGenerator(in_ch=2 * Z_CONTEXT + 1).to(device)\n        checkpoint = torch.load(path, map_location=device)\n        state_dict = checkpoint[\"state_dict\"] if \"state_dict\" in checkpoint else checkpoint\n    \n        new_state_dict = OrderedDict()\n        for k, v in state_dict.items():\n            name = k[7:] if k.startswith(\"module.\") else k\n            new_state_dict[name] = v\n            # print(name)  # uncomment if you want to inspect keys\n    \n        model.load_state_dict(new_state_dict)\n        model.eval()\n        return model\n    \n    \n    # ==========================================\n    # 3. UTILS\n    # ==========================================\n    from scipy import ndimage\n    \n    def read_volume(path: Path):\n        with Image.open(str(path)) as img:\n            frames = [np.array(frame) for frame in ImageSequence.Iterator(img)]\n        vol = np.stack(frames, axis=0)\n        if vol.ndim == 2:\n            vol = vol[None, ...]\n        return vol\n    \n    \n    def apply_cc_filter(mask: np.ndarray, min_size: int = MIN_CC_SIZE):\n        labeled, num_features = ndimage.label(mask)\n        if num_features == 0:\n            return mask\n        sizes = np.bincount(labeled.ravel())\n        too_small = sizes < min_size\n        remove_mask = too_small[labeled]\n        mask[remove_mask] = 0\n        return mask\n    def predict_with_tta(model, inp, device):\n        \"\"\"\n        Inputs:\n            model: The Pix2PixGenerator\n            inp: Tensor of shape (1, C, H, W)\n        Returns:\n            mean_pred: Averaged (H, W) prediction\n        \"\"\"\n        logits = []\n    \n        # 1. Original Prediction\n        with torch.no_grad():\n            logits.append(torch.sigmoid(model(inp)).cpu().numpy()[0, 0])\n    \n        # 2. Horizontal & Vertical Flips\n        # dims=(2, 3) corresponds to H and W for a (1, C, H, W) tensor\n        for dims in [(2,), (3,), (2, 3)]:\n            img_f = torch.flip(inp, dims=dims)\n            with torch.no_grad():\n                p = torch.sigmoid(model(img_f)).cpu().numpy()[0, 0]\n            \n            # Reverse flip to align with original\n            # Note: In numpy, after [0,0] indexing, H,W are now axes (0, 1)\n            np_dims = tuple(d - 2 for d in dims) \n            p = np.flip(p, axis=np_dims)\n            logits.append(p)\n    \n        # 3. Axial Rotations (90, 180, 270 degrees)\n        for k in [1, 2, 3]:\n            img_r = torch.rot90(inp, k=k, dims=(2, 3))\n            with torch.no_grad():\n                p = torch.sigmoid(model(img_r)).cpu().numpy()[0, 0]\n            \n            # Reverse rotation\n            p = np.rot90(p, k=-k, axes=(0, 1))\n            logits.append(p)\n    \n        return np.mean(logits, axis=0)\n    \n    # ==========================================\n    # 4. FULL-SLICE 2.5D INFERENCE\n    # ==========================================\n    def run_full_submission(model, device):\n        test_files = list(TEST_IMG_DIR.glob(\"*.tif\"))\n        processed_tifs = []\n    \n        for tif_path in test_files:\n            print(f\"--- Processing {tif_path.name} ---\")\n            vol = read_volume(tif_path)          # (Z, H, W)\n            z_max, h, w = vol.shape\n    \n            # Global normalization\n            vol_f = vol.astype(np.float32)\n            v_mean = vol_f.mean()\n            v_std = vol_f.std() + 1e-6\n            vol_f = (vol_f - v_mean) / v_std\n    \n            # Pad Z only (reflection)\n            vol_padded = np.pad(\n                vol_f,\n                ((Z_CONTEXT, Z_CONTEXT), (0, 0), (0, 0)),\n                mode=\"reflect\",\n            )\n    \n            output_volume = np.zeros((z_max, h, w), dtype=np.uint8)\n    \n            for z in tqdm(range(z_max)):\n                z_start = z\n                z_end = z + 2 * Z_CONTEXT + 1\n                slice_stack = vol_padded[z_start:z_end]          # (7, H, W)\n    \n                inp = torch.from_numpy(slice_stack).unsqueeze(0).to(device)  # (1, 7, H, W)\n                with torch.no_grad():\n                    # pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]      # (H, W)\n                    # OLD: pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]\n                    # NEW: \n                    pred = predict_with_tta(model, inp, device)\n    \n                mask = (pred > THRESHOLD).astype(np.uint8)\n                mask = apply_cc_filter(mask, min_size=MIN_CC_SIZE)\n                output_volume[z] = mask\n    \n            out_name = tif_path.name\n            tifffile.imwrite(out_name, output_volume, compression=\"deflate\")\n            processed_tifs.append(out_name)\n\n    \n        with zipfile.ZipFile(\"submission.zip\", \"w\") as zipf:\n            for f in processed_tifs:\n                zipf.write(f)\n                os.remove(f)\n    \n        print(\"submission.zip created.\")\n    \n    \n    if __name__ == \"__main__\":\n        netG = load_generator(CHECKPOINT_PATH, DEVICE)\n        run_full_submission(netG, DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.039070Z","iopub.execute_input":"2026-03-02T15:40:24.039275Z","iopub.status.idle":"2026-03-02T15:40:24.061002Z","shell.execute_reply.started":"2026-03-02T15:40:24.039243Z","shell.execute_reply":"2026-03-02T15:40:24.060262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if single_model: \n    import tifffile\n    import matplotlib.pyplot as plt\n    import zipfile\n    import os\n    import numpy as np\n    import random\n    \n    def verify_and_visualize_submission(zip_path='submission.zip'):\n        if not os.path.exists(zip_path):\n            print(f\"❌ {zip_path} not found.\")\n            return\n    \n        extract_path = 'temp_viz'\n        with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n            zip_ref.extractall(extract_path)\n        \n        tif_files = [f for f in os.listdir(extract_path) if f.endswith('.tif')]\n        \n        for tif_file in tif_files:\n            file_path = os.path.join(extract_path, tif_file)\n            # Load the generated volume\n            pred_volume = tifffile.imread(file_path)\n            z_max = pred_volume.shape[0]\n    \n            # 1. Check if values are binary (0 or 1)\n            unique_vals = np.unique(pred_volume)\n            print(f\"\\n--- Checking {tif_file} ---\")\n            print(f\"Shape: {pred_volume.shape} | Unique values: {unique_vals}\")\n            \n            if not np.all(np.isin(unique_vals, [0, 1])):\n                print(\"⚠️ WARNING: Found values other than 0 and 1!\")\n            else:\n                print(\"✅ Data is correctly binary (0s and 1s).\")\n    \n            # 2. Select indices: 5 from first 50, 5 from middle, 5 from last 50\n            first_indices = random.sample(range(0, min(50, z_max)), 5)\n            mid_start = max(0, (z_max // 2) - 25)\n            mid_end = min(z_max, (z_max // 2) + 25)\n            mid_indices = random.sample(range(mid_start, mid_end), 5)\n            last_indices = random.sample(range(max(0, z_max - 50), z_max), 5)\n    \n            all_indices = sorted(first_indices + mid_indices + last_indices)\n            \n            # 3. Plotting Grid (3 rows for the 3 sections)\n            fig, axes = plt.subplots(3, 5, figsize=(20, 12))\n            fig.suptitle(f\"Sampled Slices from {tif_file}\\n(Rows: Start, Middle, End)\", fontsize=16)\n    \n            for i, idx in enumerate(all_indices):\n                row = i // 5\n                col = i % 5\n                \n                # Note: We display with cmap='gray' so 1 is white and 0 is black\n                axes[row, col].imshow(pred_volume[idx], cmap='gray')\n                axes[row, col].set_title(f\"z={idx}\")\n                axes[row, col].axis('off')\n    \n            plt.tight_layout()\n            plt.show()\n    \n    # Run the verification\n    verify_and_visualize_submission()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.226972Z","iopub.execute_input":"2026-03-02T15:40:24.227252Z","iopub.status.idle":"2026-03-02T15:40:24.235695Z","shell.execute_reply.started":"2026-03-02T15:40:24.227214Z","shell.execute_reply":"2026-03-02T15:40:24.235111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if single_model: \n\n    def read_volume(path: Path):\n        try:\n            with Image.open(str(path)) as img:\n                frames = [np.array(frame) for frame in ImageSequence.Iterator(img)]\n                vol = np.stack(frames, axis=0)\n        except Exception: return np.zeros((1, PATCH_SIZE, PATCH_SIZE), dtype=np.uint8)\n        if vol.ndim == 2: vol = vol[None, ...]\n        return vol\n    def apply_cc_filter(mask: np.ndarray, min_size: int = 100):\n        \"\"\"\n        Removes small disconnected components from the binary mask.\n        min_size: Minimum pixel area to keep a component.\n        \"\"\"\n        # Label distinct islands of pixels\n        labeled, num_features = ndimage.label(mask)\n        if num_features == 0:\n            return mask\n        \n        # Count pixels in each component\n        component_sizes = np.bincount(labeled.ravel())\n        \n        # Create a mask of components that meet the size requirement\n        too_small = component_sizes < min_size\n        remove_mask = too_small[labeled]\n        \n        mask[remove_mask] = 0\n        return mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.236845Z","iopub.execute_input":"2026-03-02T15:40:24.237032Z","iopub.status.idle":"2026-03-02T15:40:24.249909Z","shell.execute_reply.started":"2026-03-02T15:40:24.237014Z","shell.execute_reply":"2026-03-02T15:40:24.249192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if single_model: \n    import tifffile\n    import matplotlib.pyplot as plt\n    import zipfile\n    import os\n    import numpy as np\n    \n    def visualize_submission_samples(zip_path='submission.zip', num_samples=3):\n        # 1. Extract the submission files\n        extract_path = 'temp_viz'\n        with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n            zip_ref.extractall(extract_path)\n        \n        tif_files = [f for f in os.listdir(extract_path) if f.endswith('.tif')]\n        \n        for tif_file in tif_files[:num_samples]:\n            file_path = os.path.join(extract_path, tif_file)\n            \n            # Load the generated volume (Binary masks after CC filter)\n            pred_volume = tifffile.imread(file_path)\n            \n            # Select a middle slice for visualization\n            mid_idx = pred_volume.shape[0] // 2\n            pred_slice = pred_volume[mid_idx]\n            \n            # Load original image for context (if available in test_images)\n            test_img_path = DATA_PATH / 'test_images' / tif_file\n            if test_img_path.exists():\n                orig_volume = read_volume(test_img_path)\n                orig_slice = orig_volume[mid_idx]\n            else:\n                orig_slice = np.zeros_like(pred_slice)\n    \n            # Plotting\n            fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n            \n            axes[0].imshow(orig_slice, cmap='gray')\n            axes[0].set_title(f\"Original Slice (z={mid_idx})\")\n            \n            axes[1].imshow(pred_slice, cmap='magma')\n            axes[1].set_title(\"Prediction (After CC Filter)\")\n            \n            # Overlay to see alignment\n            axes[2].imshow(orig_slice, cmap='gray')\n            axes[2].imshow(pred_slice, cmap='jet', alpha=0.4)\n            axes[2].set_title(\"Overlay (Surface Alignment)\")\n            \n            for ax in axes: ax.axis('off')\n            plt.suptitle(f\"Sample: {tif_file}\")\n            plt.show()\n    \n    # Run the visualization\n    if os.path.exists('submission.zip'):\n        visualize_submission_samples()\n    else:\n        print(\"submission.zip not found. Run inference first!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.250694Z","iopub.execute_input":"2026-03-02T15:40:24.251266Z","iopub.status.idle":"2026-03-02T15:40:24.263145Z","shell.execute_reply.started":"2026-03-02T15:40:24.251237Z","shell.execute_reply":"2026-03-02T15:40:24.262508Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Ensemble Test","metadata":{}},{"cell_type":"code","source":"import time","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.263855Z","iopub.execute_input":"2026-03-02T15:40:24.264025Z","iopub.status.idle":"2026-03-02T15:40:24.277056Z","shell.execute_reply.started":"2026-03-02T15:40:24.264008Z","shell.execute_reply":"2026-03-02T15:40:24.276411Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport zipfile\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom pathlib import Path\nfrom PIL import Image, ImageSequence\nfrom tqdm import tqdm\nfrom collections import OrderedDict\nimport tifffile\nfrom scipy import ndimage\n\n# ==========================================\n# 1. CONFIG\n# ==========================================\nZ_CONTEXT = 3\nTHRESHOLD = 0.055\nMIN_CC_SIZE = 3\n\nDATA_PATH = Path(\"/kaggle/input/vesuvius-challenge-surface-detection\")\nTEST_IMG_DIR = DATA_PATH / \"test_images\"\n\n# UPDATED: List all checkpoints you want to include in the ensemble\nCHECKPOINT_PATHS = [\n    Path(\"/kaggle/input/vesuvius-competition-models/results/checkpoints/best_model.pth\"),\n    Path(\"/kaggle/input/vesuvius-competition-models/results/checkpoints/latest_model.pth\"),\n    Path(\"/kaggle/input/vesuvius-competition-models/results/checkpoints/model_epoch_20.pth\"),\n    Path(\"/kaggle/input/vesuvius-competition-models/results/checkpoints/model_epoch_30.pth\"),\n    Path(\"/kaggle/input/vesuvius-competition-models/results/checkpoints/model_epoch_40.pth\"),\n    Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/best_model.pth\"),\n    Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/model_epoch_40.pth\"),\n    Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/model_epoch_60.pth\"),\n    Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/model_epoch_100.pth\"),\n    Path(\"/kaggle/input/pix2pix-2-5d-training-discriminator/checkpoints/model_epoch_150.pth\"),\n    # Path(\"/kaggle/input/notebooks/crischir/pix2pix-2-5d-retraining-discriminator/checkpoints/model_epoch_120.pth\"),\n    # Path(\"/kaggle/input/notebooks/crischir/pix2pix-2-5d-retraining-discriminator/checkpoints/model_epoch_130.pth\"),  \n    # Path(\"/kaggle/input/notebooks/crischir/pix2pix-2-5d-retraining-discriminator/checkpoints/netD_epoch_140.pth\"),\n]\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# ==========================================\n# 2. MODEL\n# ==========================================\nclass Pix2PixGenerator(nn.Module):\n    def __init__(self, in_ch=7, out_ch=1):\n        super().__init__()\n        self.e1 = nn.Sequential(nn.Conv2d(in_ch, 64, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True))\n        self.e2 = nn.Sequential(nn.Conv2d(64, 128, 4, 2, 1), nn.BatchNorm2d(128), nn.LeakyReLU(0.2, inplace=True))\n        self.e3 = nn.Sequential(nn.Conv2d(128, 256, 4, 2, 1), nn.BatchNorm2d(256), nn.LeakyReLU(0.2, inplace=True))\n        self.d1 = nn.Sequential(nn.ConvTranspose2d(256, 128, 4, 2, 1), nn.BatchNorm2d(128), nn.ReLU(inplace=True))\n        self.d2 = nn.Sequential(nn.ConvTranspose2d(256, 64, 4, 2, 1), nn.BatchNorm2d(64), nn.ReLU(inplace=True))\n        self.final = nn.ConvTranspose2d(128, out_ch, 4, 2, 1)\n\n    def forward(self, x):\n        en1 = self.e1(x)\n        en2 = self.e2(en1)\n        en3 = self.e3(en2)\n        de1 = self.d1(en3)\n        de2 = self.d2(torch.cat([de1, en2], 1))\n        return self.final(torch.cat([de2, en1], 1))\n\n# UPDATED: Load multiple models into a list\ndef load_ensemble(paths, device):\n    models = []\n    for path in paths:\n        if not path.exists():\n            print(f\"Warning: Checkpoint {path} not found. Skipping.\")\n            continue\n        model = Pix2PixGenerator(in_ch=2 * Z_CONTEXT + 1).to(device)\n        checkpoint = torch.load(path, map_location=device)\n        state_dict = checkpoint[\"state_dict\"] if \"state_dict\" in checkpoint else checkpoint\n        \n        new_state_dict = OrderedDict()\n        for k, v in state_dict.items():\n            name = k[7:] if k.startswith(\"module.\") else k\n            new_state_dict[name] = v\n        \n        model.load_state_dict(new_state_dict)\n        model.eval()\n        models.append(model)\n        print(f\"Loaded: {path.name}\")\n    return models\n\n# ==========================================\n# 3. UTILS \n# ==========================================\ndef read_volume(path: Path):\n    with Image.open(str(path)) as img:\n        frames = [np.array(frame) for frame in ImageSequence.Iterator(img)]\n    vol = np.stack(frames, axis=0)\n    if vol.ndim == 2: vol = vol[None, ...]\n    return vol\n\ndef apply_cc_filter(mask: np.ndarray, min_size: int = MIN_CC_SIZE):\n    labeled, num_features = ndimage.label(mask)\n    if num_features == 0: return mask\n    sizes = np.bincount(labeled.ravel())\n    mask[sizes[labeled] < min_size] = 0\n    return mask\ndef predict_with_tta(model, inp, device):\n        \"\"\"\n        Inputs:\n            model: The Pix2PixGenerator\n            inp: Tensor of shape (1, C, H, W)\n        Returns:\n            mean_pred: Averaged (H, W) prediction\n        \"\"\"\n        logits = []\n    \n        # 1. Original Prediction\n        with torch.no_grad():\n            logits.append(torch.sigmoid(model(inp)).cpu().numpy()[0, 0])\n    \n        # 2. Horizontal & Vertical Flips\n        # dims=(2, 3) corresponds to H and W for a (1, C, H, W) tensor\n        for dims in [(2,), (3,), (2, 3)]:\n            img_f = torch.flip(inp, dims=dims)\n            with torch.no_grad():\n                p = torch.sigmoid(model(img_f)).cpu().numpy()[0, 0]\n            \n            # Reverse flip to align with original\n            # Note: In numpy, after [0,0] indexing, H,W are now axes (0, 1)\n            np_dims = tuple(d - 2 for d in dims) \n            p = np.flip(p, axis=np_dims)\n            logits.append(p)\n    \n        # 3. Axial Rotations (90, 180, 270 degrees)\n        for k in [1, 2, 3]:\n            img_r = torch.rot90(inp, k=k, dims=(2, 3))\n            with torch.no_grad():\n                p = torch.sigmoid(model(img_r)).cpu().numpy()[0, 0]\n            \n            # Reverse rotation\n            p = np.rot90(p, k=-k, axes=(0, 1))\n            logits.append(p)\n    \n        return np.mean(logits, axis=0)\n# ==========================================\n# 4. FULL-SLICE 2.5D ENSEMBLE INFERENCE\n# ==========================================\ndef run_full_submission(models, device):\n    test_files = list(TEST_IMG_DIR.glob(\"*.tif\"))\n    processed_tifs = []\n\n    for tif_path in test_files:\n        print(f\"--- Processing {tif_path.name} with Ensemble of {len(models)} ---\")\n        vol = read_volume(tif_path)\n        z_max, h, w = vol.shape\n\n        vol_f = vol.astype(np.float32)\n        v_mean, v_std = vol_f.mean(), vol_f.std() + 1e-6\n        vol_f = (vol_f - v_mean) / v_std\n\n        vol_padded = np.pad(vol_f, ((Z_CONTEXT, Z_CONTEXT), (0, 0), (0, 0)), mode=\"reflect\")\n        output_volume = np.zeros((z_max, h, w), dtype=np.uint8)\n\n        # for z in tqdm(range(z_max)):\n        #     z_start, z_end = z, z + 2 * Z_CONTEXT + 1\n        #     slice_stack = vol_padded[z_start:z_end]\n        #     inp = torch.from_numpy(slice_stack).unsqueeze(0).to(device)\n\n        #     # --- SOFT VOTING ENSEMBLE ---\n        #     ensemble_pred = np.zeros((h, w), dtype=np.float32)\n            \n        #     # with torch.no_grad():\n        #     #     for model in models:\n        #     #         pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]\n        #     #         ensemble_pred += pred\n                \n        #     #     # Average the sigmoid probabilities\n        #     #     ensemble_pred /= len(models)\n\n\n        #     with torch.no_grad(): \n        #         for model in models:\n        #             # pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]      # (H, W)\n        #             # OLD: pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]\n        #             # NEW: \n        #             pred = predict_with_tta(model, inp, device)\n        #             ensemble_pred += pred\n\n            \n        #     mask = (ensemble_pred > THRESHOLD).astype(np.uint8)\n        #     mask = apply_cc_filter(mask, min_size=MIN_CC_SIZE)\n        #     output_volume[z] = mask\n        for z in tqdm(range(z_max)):\n            z_start, z_end = z, z + 2 * Z_CONTEXT + 1\n            slice_stack = vol_padded[z_start:z_end]\n            \n            # FIX 1: Ensure normalization matches TRAINING (per-stack)\n            # Instead of global normalization, normalize this specific stack\n            slice_stack = slice_stack.astype(np.float32)\n            s_mean, s_std = slice_stack.mean(), slice_stack.std() + 1e-6\n            slice_stack = (slice_stack - s_mean) / s_std\n            \n            inp = torch.from_numpy(slice_stack).unsqueeze(0).to(device)\n    \n            ensemble_pred = np.zeros((h, w), dtype=np.float32)\n            \n            with torch.no_grad(): \n                for model in models:\n                    if TTA:\n                        pred = predict_with_tta(model, inp, device)\n                        ensemble_pred += pred\n                    else:\n                        pred = torch.sigmoid(model(inp)).cpu().numpy()[0, 0]\n                        ensemble_pred += pred\n    \n                # FIX 2: Correct the Soft Voting (Averaging)\n                ensemble_pred /= len(models) \n    \n            # Thresholding the average probability\n            mask = (ensemble_pred > THRESHOLD).astype(np.uint8)\n            mask = apply_cc_filter(mask, min_size=MIN_CC_SIZE)\n            output_volume[z] = mask\n        out_name = tif_path.name\n        tifffile.imwrite(out_name, output_volume, compression=\"deflate\")\n        processed_tifs.append(out_name)\n\n    with zipfile.ZipFile(\"submission.zip\", \"w\") as zipf:\n        for f in processed_tifs:\n            zipf.write(f)\n            os.remove(f)\n    print(\"Ensemble submission.zip created.\")\nstart_time = time.time()\nif __name__ == \"__main__\":\n    # Load all models defined in CONFIG\n    ensemble_models = load_ensemble(CHECKPOINT_PATHS, DEVICE)\n    \n    if not ensemble_models:\n        print(\"Error: No models loaded. Check your CHECKPOINT_PATHS.\")\n    else:\n        run_full_submission(ensemble_models, DEVICE)\nend_time = time.time()\nprint(f\"Total execution time: {end_time - start_time:.2f} seconds\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:40:24.278772Z","iopub.execute_input":"2026-03-02T15:40:24.279015Z","iopub.status.idle":"2026-03-02T15:41:33.333362Z","shell.execute_reply.started":"2026-03-02T15:40:24.278996Z","shell.execute_reply":"2026-03-02T15:41:33.332700Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tifffile\nimport matplotlib.pyplot as plt\nimport zipfile\nimport os\nimport numpy as np\nimport random\n\ndef verify_and_visualize_submission(zip_path='submission.zip'):\n    if not os.path.exists(zip_path):\n        print(f\"❌ {zip_path} not found.\")\n        return\n\n    extract_path = 'temp_viz'\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(extract_path)\n    \n    tif_files = [f for f in os.listdir(extract_path) if f.endswith('.tif')]\n    \n    for tif_file in tif_files:\n        file_path = os.path.join(extract_path, tif_file)\n        # Load the generated volume\n        pred_volume = tifffile.imread(file_path)\n        z_max = pred_volume.shape[0]\n\n        # 1. Check if values are binary (0 or 1)\n        unique_vals = np.unique(pred_volume)\n        print(f\"\\n--- Checking {tif_file} ---\")\n        print(f\"Shape: {pred_volume.shape} | Unique values: {unique_vals}\")\n        \n        if not np.all(np.isin(unique_vals, [0, 1])):\n            print(\"⚠️ WARNING: Found values other than 0 and 1!\")\n        else:\n            print(\"✅ Data is correctly binary (0s and 1s).\")\n\n        # 2. Select indices: 5 from first 50, 5 from middle, 5 from last 50\n        first_indices = random.sample(range(0, min(50, z_max)), 5)\n        mid_start = max(0, (z_max // 2) - 25)\n        mid_end = min(z_max, (z_max // 2) + 25)\n        mid_indices = random.sample(range(mid_start, mid_end), 5)\n        last_indices = random.sample(range(max(0, z_max - 50), z_max), 5)\n\n        all_indices = sorted(first_indices + mid_indices + last_indices)\n        \n        # 3. Plotting Grid (3 rows for the 3 sections)\n        fig, axes = plt.subplots(3, 5, figsize=(20, 12))\n        fig.suptitle(f\"Sampled Slices from {tif_file}\\n(Rows: Start, Middle, End)\", fontsize=16)\n\n        for i, idx in enumerate(all_indices):\n            row = i // 5\n            col = i % 5\n            \n            # Note: We display with cmap='gray' so 1 is white and 0 is black\n            axes[row, col].imshow(pred_volume[idx], cmap='gray')\n            axes[row, col].set_title(f\"z={idx}\")\n            axes[row, col].axis('off')\n\n        plt.tight_layout()\n        plt.show()\n\n# Run the verification\nverify_and_visualize_submission()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:41:33.334223Z","iopub.execute_input":"2026-03-02T15:41:33.334696Z","iopub.status.idle":"2026-03-02T15:41:35.401370Z","shell.execute_reply.started":"2026-03-02T15:41:33.334667Z","shell.execute_reply":"2026-03-02T15:41:35.400571Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Test for masking ","metadata":{}},{"cell_type":"code","source":"import zipfile\nimport io\nimport cv2\nimport numpy as np\nfrom PIL import Image\n\ndef process_tif_from_zip(zip_path, target_file_inside_zip, pages_to_process=[10, 30, 40]):\n    results = {}\n    \n    with zipfile.ZipFile(zip_path, 'r') as z:\n        with z.open(target_file_inside_zip) as f:\n            # Citim fișierul TIF (care poate avea mai multe pagini)\n            tif_data = io.BytesIO(f.read())\n            img_stack = Image.open(tif_data)\n            \n            for i in pages_to_process:\n                try:\n                    img_stack.seek(i)\n                    # Convertim pagina PIL în format OpenCV (numpy array)\n                    page = np.array(img_stack.convert('L'))\n                    \n                    # 1. Verificare format / Binarizare\n                    # Dacă imaginea nu este deja binară (0 și 255), o forțăm\n                    _, binary = cv2.threshold(page, 127, 255, cv2.THRESH_BINARY)\n                    \n                    # 2. Aplicare algoritm pentru suprafețe continue\n                    processed_mask = filter_continuous_surfaces(binary, min_area=500)\n                    \n                    results[f\"page_{i}\"] = processed_mask\n                    print(f\"Pagina {i} procesată cu succes.\")\n                    \n                except EOFError:\n                    print(f\"Fișierul are mai puține pagini decât s-a solicitat (oprit la pagina {i}).\")\n                    break\n    return results\n\ndef filter_continuous_surfaces(binary_img, min_area=500):\n    \"\"\"\n    Păstrează doar componentele albe care au o arie mai mare decât min_area.\n    \"\"\"\n    # Găsim toate obiectele albe\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary_img, connectivity=8)\n    \n    # Cream o mască goală (neagră)\n    mask = np.zeros_like(binary_img)\n    \n    # Parcurgem obiectele găsite (sărim peste 0 care este fundalul)\n    for i in range(1, num_labels):\n        area = stats[i, cv2.CC_STAT_AREA]\n        \n        # Dacă suprafața este suficient de mare, o considerăm \"continuă\"\n        if area >= min_area:\n            mask[labels == i] = 255\n            \n    return mask\n\n# --- EXEMPLU DE UTILIZARE ---\nzip_path = 'submission.zip'\ntif_name = '1407735.tif'\nmasti_pagini = process_tif_from_zip(zip_path, tif_name)\nif masti_pagini:\n    # Luăm prima cheie disponibilă în dicționar (ex: 'page_10')\n    prima_cheie = list(masti_pagini.keys())[0]\n    print(f\"Afișez: {prima_cheie}\")\n    \n    cv2.imshow('Rezultat Procesare', masti_pagini[prima_cheie])\n    cv2.waitKey(0)\n    cv2.destroyAllWindows()\nelse:\n    print(\"Dicționarul este gol. Nicio pagină nu a fost procesată.\")\n# Pentru vizualizare (opțional):\ncv2.imshow('Pagina 0 Procesata', masti_pagini['page_0'])\ncv2.waitKey(0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:44:49.827683Z","iopub.execute_input":"2026-03-02T15:44:49.828313Z","execution_failed":"2026-03-02T15:44:52.142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport zipfile\nimport io\nfrom PIL import Image\n\n# def filter_continuous_surfaces(binary_img, min_area=500):\n#     \"\"\"\n#     Versiune optimizată care nu blochează kernel-ul.\n#     Folosește vectorizare în loc de bucle for.\n#     \"\"\"\n#     # Asigurăm formatul corect pentru OpenCV\n#     if binary_img.dtype != np.uint8:\n#         binary_img = binary_img.astype(np.uint8)\n    \n#     # Analiza componentelor conectate\n#     num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary_img, connectivity=8)\n    \n#     # stats[:, 4] este coloana cu ariile fiecărei componente\n#     # Creăm un vector boolean cu True pentru componentele mari\n#     large_areas = stats[:, cv2.CC_STAT_AREA] >= min_area\n    \n#     # Fundalul (label 0) trebuie să rămână mereu negru (False)\n#     large_areas[0] = False\n    \n#     # Reconstruim masca: pixelii primesc 255 dacă label-ul lor este în lista 'large_areas'\n#     mask = np.where(large_areas[labels], 255, 0).astype(np.uint8)\n    \n#     return mask\ndef filter_continuous_surfaces(binary_img, min_area=50): # Prag redus pentru 320x320\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary_img, connectivity=8)\n    \n    if num_labels <= 1: # Doar fundalul a fost găsit\n        return np.zeros_like(binary_img)\n\n    # Debug: Afișează cea mai mare arie găsită (fără fundal)\n    max_found_area = np.max(stats[1:, cv2.CC_STAT_AREA])\n    print(f\"Cea mai mare suprafață găsită are {max_found_area} pixeli.\")\n\n    large_areas = stats[:, cv2.CC_STAT_AREA] >= min_area\n    large_areas[0] = False\n    \n    mask = np.where(large_areas[labels], 255, 0).astype(np.uint8)\n    return mask\ndef process_tif_from_zip(zip_path, target_file, pages=[0]):\n    results = {}\n    try:\n        with zipfile.ZipFile(zip_path, 'r') as z:\n            with z.open(target_file) as f:\n                tif_bytes = io.BytesIO(f.read())\n                img_stack = Image.open(tif_bytes)\n                \n                for p in pages:\n                    try:\n                        img_stack.seek(p)\n                        # Convertim în grayscale (L) și apoi în array numpy\n                        frame = np.array(img_stack.convert('L'))\n                        \n                        # Binarizare automată (Otsu) sau prag fix\n                        _, binary = cv2.threshold(frame, 127, 255, cv2.THRESH_BINARY)\n                        \n                        # Aplicăm filtrul de continuitate\n                        results[f'page_{p}'] = filter_continuous_surfaces(binary, min_area=1000)\n                        print(f\"Pagina {p} procesată.\")\n                    except EOFError:\n                        break\n    except Exception as e:\n        print(f\"Eroare la procesare: {e}\")\n    return results\n\n# --- EXECUTARE ---\nzip_path = 'submission.zip'\ntif_name = '1407735.tif'\n\n# Procesăm paginile dorite\nmasti_pagini = process_tif_from_zip(zip_path, tif_name, pages=[0, 10, 30, 40])\n\n# VIZUALIZARE (Folosim matplotlib pentru a evita crash-ul ferestrelor cv2)\nif masti_pagini:\n    for label, img in masti_pagini.items():\n        plt.figure(figsize=(8, 8))\n        plt.imshow(img, cmap='gray')\n        plt.title(f\"Masca Suprafete Continue - {label}\")\n        plt.axis('off')\n        plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:49:38.952906Z","iopub.execute_input":"2026-03-02T15:49:38.953233Z","iopub.status.idle":"2026-03-02T15:49:39.521161Z","shell.execute_reply.started":"2026-03-02T15:49:38.953207Z","shell.execute_reply":"2026-03-02T15:49:39.520374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def debug_page_format(img_stack, page_index):\n#     img_stack.seek(page_index)\n    \n#     # 1. Citire brută\n#     raw_data = np.array(img_stack)\n#     print(f\"--- Diagnostic Pagina {page_index} ---\")\n#     print(f\"Format original PIL: {img_stack.mode}\") # L, RGB, I;16, etc.\n#     print(f\"Shape: {raw_data.shape}\")\n#     print(f\"Valori: Min={raw_data.min()}, Max={raw_data.max()}, Media={raw_data.mean():.2f}\")\n    \n#     # 2. Conversie forțată la Grayscale 8-bit\n#     frame_8bit = np.array(img_stack.convert('L'))\n    \n#     # 3. Vizualizare histogramă (ajută să vedem unde sunt pixelii)\n#     plt.figure(figsize=(12, 4))\n#     plt.subplot(1, 3, 1)\n#     plt.imshow(frame_8bit, cmap='gray')\n#     plt.title(\"Imagine Grayscale\")\n    \n#     plt.subplot(1, 3, 2)\n#     plt.hist(frame_8bit.ravel(), bins=256, range=(0, 256))\n#     plt.title(\"Histogramă Pixeli\")\n    \n#     # 4. Binarizare automată (Otsu)\n#     _, binary = cv2.threshold(frame_8bit, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)\n#     plt.subplot(1, 3, 3)\n#     plt.imshow(binary, cmap='gray')\n#     plt.title(\"Binarizare Otsu\")\n#     plt.show()\n\n# # Folosește-l astfel:\n# debug_page_format(img_stack, 10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:53:45.783275Z","iopub.execute_input":"2026-03-02T15:53:45.783612Z","iopub.status.idle":"2026-03-02T15:53:45.787843Z","shell.execute_reply.started":"2026-03-02T15:53:45.783584Z","shell.execute_reply":"2026-03-02T15:53:45.787088Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport zipfile\nimport io\nfrom PIL import Image\n\ndef diagnose_and_fix_black_pages(zip_path, tif_name, page_idx=0):\n    try:\n        with zipfile.ZipFile(zip_path, 'r') as z:\n            with z.open(tif_name) as f:\n                tif_bytes = io.BytesIO(f.read())\n                # Deschidem stack-ul TIF\n                with Image.open(tif_bytes) as img_stack:\n                    img_stack.seek(page_idx)\n                    \n                    # 1. Extragere date brute\n                    raw_frame = np.array(img_stack)\n                    \n                    print(f\"--- Diagnostic Pagina {page_idx} ---\")\n                    print(f\"Sursă: {tif_name} | Rezoluție: {raw_frame.shape}\")\n                    print(f\"Valori brute: Min={raw_frame.min()}, Max={raw_frame.max()}\")\n\n                    # 2. CONVERSIE ȘI NORMALIZARE (Fix pentru imagini \"negre\")\n                    # Dacă Max e mic (ex: 1 sau 2), transformăm gama în 0-255\n                    if raw_frame.max() > 0:\n                        frame_8bit = cv2.normalize(raw_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)\n                    else:\n                        frame_8bit = np.zeros((320, 320), dtype=np.uint8)\n                        print(\"ATENȚIE: Pagina este complet goală (toți pixelii sunt 0).\")\n\n                    # 3. BINARIZARE ADAPTIVĂ\n                    # La 320x320, Otsu poate eșua dacă imaginea e zgomotoasă\n                    _, thresh_otsu = cv2.threshold(frame_8bit, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)\n\n                    # 4. VIZUALIZARE\n                    plt.figure(figsize=(15, 5))\n                    \n                    plt.subplot(1, 3, 1)\n                    plt.imshow(raw_frame, cmap='gray')\n                    plt.title(\"1. Brut (Poate părea negru)\")\n                    \n                    plt.subplot(1, 3, 2)\n                    plt.imshow(frame_8bit, cmap='gray')\n                    plt.title(\"2. Normalizat (Vizibil)\")\n                    \n                    plt.subplot(1, 3, 3)\n                    plt.imshow(thresh_otsu, cmap='gray')\n                    plt.title(\"3. Binarizat (Pentru Mască)\")\n                    \n                    plt.show()\n                    \n                    return frame_8bit\n\n    except Exception as e:\n        print(f\"Eroare critică: {e}\")\n\n# --- RULEAZĂ TESTUL ACUM ---\n# Înlocuiește cu numele tale de fișiere\nzip_path = 'submission.zip'\ntif_name = '1407735.tif'\nimagine_vizibila = diagnose_and_fix_black_pages(zip_path, tif_name, page_idx=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:53:53.842415Z","iopub.execute_input":"2026-03-02T15:53:53.842738Z","iopub.status.idle":"2026-03-02T15:53:54.231332Z","shell.execute_reply.started":"2026-03-02T15:53:53.842711Z","shell.execute_reply":"2026-03-02T15:53:54.230713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\n\ndef process_binary_tif(raw_frame, min_area_pixels=20):\n    # 1. Scalare: Convertim valorile din {0, 1} în {0, 255}\n    # Dacă Max=1, înmulțim cu 255 pentru a face pixelii vizibili\n    if raw_frame.max() == 1:\n        frame_8bit = (raw_frame * 255).astype(np.uint8)\n    else:\n        # În caz că sunt deja 0-255 sau alte valori\n        frame_8bit = cv2.normalize(raw_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)\n\n    # 2. Identificarea componentelor conectate\n    # La 320x320, o suprafață \"continuă\" este mult mai mică decât la rezoluții mari\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(frame_8bit, connectivity=8)\n    \n    # 3. Filtrare vectorizată (previne crash-ul kernelului)\n    # Păstrăm doar obiectele care au cel puțin min_area_pixels\n    keep_labels = np.where(stats[:, cv2.CC_STAT_AREA] >= min_area_pixels)[0]\n    \n    # Eliminăm label-ul 0 (fundalul)\n    keep_labels = keep_labels[keep_labels != 0]\n    \n    # Construim masca finală\n    mask = np.isin(labels, keep_labels).astype(np.uint8) * 255\n    \n    return frame_8bit, mask\n\n# --- TESTARE ---\n# Presupunem că 'raw_frame' este matricea ta cu Max=1\noriginal_vizibil, masca_finala = process_binary_tif(raw_frame, min_area_pixels=15)\n\nplt.figure(figsize=(10, 5))\nplt.subplot(1, 2, 1)\nplt.imshow(original_vizibil, cmap='gray')\nplt.title(\"Imagine Scalată (0-255)\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(masca_finala, cmap='gray')\nplt.title(f\"Masca (Suprafețe > 15px)\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:54:47.353706Z","iopub.execute_input":"2026-03-02T15:54:47.354446Z","iopub.status.idle":"2026-03-02T15:54:47.363039Z","shell.execute_reply.started":"2026-03-02T15:54:47.354418Z","shell.execute_reply":"2026-03-02T15:54:47.362163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport zipfile\nimport io\nfrom PIL import Image\n\ndef process_binary_tif_v3(zip_path, tif_name, page_idx=10, min_area=20):\n    try:\n        with zipfile.ZipFile(zip_path, 'r') as z:\n            with z.open(tif_name) as f:\n                tif_bytes = io.BytesIO(f.read())\n                with Image.open(tif_bytes) as img_stack:\n                    img_stack.seek(page_idx)\n                    \n                    # 1. Extragere date brute (aici se creează raw_frame)\n                    raw_frame = np.array(img_stack)\n                    \n                    # 2. Conversie la 0-255 (deoarece Max=1)\n                    if raw_frame.max() == 1:\n                        frame_8bit = (raw_frame * 255).astype(np.uint8)\n                    else:\n                        frame_8bit = cv2.normalize(raw_frame, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)\n\n                    # 3. Identificarea componentelor conectate\n                    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(frame_8bit, connectivity=8)\n                    \n                    # 4. Filtrare arii (pentru 320x320, min_area trebuie să fie mică)\n                    keep_labels = np.where(stats[:, cv2.CC_STAT_AREA] >= min_area)[0]\n                    keep_labels = keep_labels[keep_labels != 0] # scoatem fundalul\n                    \n                    # 5. Creare Mască\n                    mask = np.isin(labels, keep_labels).astype(np.uint8) * 255\n                    \n                    # Vizualizare Rezultate\n                    plt.figure(figsize=(12, 5))\n                    plt.subplot(1, 2, 1)\n                    plt.imshow(frame_8bit, cmap='gray')\n                    plt.title(f\"Original Scalat (Pagina {page_idx})\")\n                    \n                    plt.subplot(1, 2, 2)\n                    plt.imshow(mask, cmap='gray')\n                    plt.title(f\"Masca (Suprafețe > {min_area} px)\")\n                    plt.show()\n                    \n                    return frame_8bit, mask\n\n    except Exception as e:\n        print(f\"Eroare: {e}\")\n        return None, None\n\n# --- EXECUTARE ---\n# Asigură-te că fișierele există în directorul de lucru\nzip_path = 'submission.zip'\ntif_name = '1407735.tif'\n\n# Acum apelăm funcția care gestionează intern deschiderea fișierului\nimg_vizibila, masca_finala = process_binary_tif_v3(zip_path, tif_name, page_idx=50, min_area=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-02T15:56:16.822858Z","iopub.execute_input":"2026-03-02T15:56:16.823461Z","iopub.status.idle":"2026-03-02T15:56:17.102760Z","shell.execute_reply.started":"2026-03-02T15:56:16.823434Z","shell.execute_reply":"2026-03-02T15:56:17.102023Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}