{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":117682,"databundleVersionId":14443416,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"\nVesuvius Challenge - Document Line Segmentation\n\"\"\"\n\nimport numpy as np\nfrom pathlib import Path\nfrom PIL import Image\nimport zipfile\nimport tifffile\nimport os\nfrom tqdm import tqdm\nfrom scipy import ndimage\nfrom skimage.filters import threshold_otsu, threshold_multiotsu\nfrom skimage.morphology import remove_small_objects, binary_closing, binary_dilation, binary_erosion, remove_small_holes, skeletonize, disk, rectangle\nfrom skimage.measure import label\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# ============================================================================\n# CONFIGURATION - WITH BETTER PATH HANDLING\n# ============================================================================\nBASE_DIR = Path(\"/kaggle/input/vesuvius-challenge-surface-detection\")\nTEST_DIR = BASE_DIR / \"test_images\"\nTEST_CSV = BASE_DIR / \"test.csv\"\nOUTPUT_ZIP = \"submission.zip\"\n\nprint(\"Checking directory structure...\")\nprint(f\"Base directory exists: {BASE_DIR.exists()}\")\nif BASE_DIR.exists():\n    print(\"Files in base directory:\")\n    for f in BASE_DIR.iterdir():\n        print(f\"  - {f.name}\")\n\n# ============================================================================\n# DOCUMENT LINE SEGMENTATION FUNCTIONS\n# ============================================================================\n\ndef load_volume(path):\n    \"\"\"Load 3D TIFF volume - KEEPING YOUR WORKING METHOD\"\"\"\n    try:\n        return tifffile.imread(path)\n    except:\n        with Image.open(path) as img:\n            frames = []\n            for i in range(img.n_frames):\n                img.seek(i)\n                frames.append(np.array(img))\n        return np.stack(frames)\n\ndef segment_text_lines(volume):\n    \"\"\"\n    Segment text lines from scroll/document volume\n    \"\"\"\n    if len(volume.shape) == 3:\n        n_slices = min(5, volume.shape[0])\n        avg_slice = np.mean(volume[:n_slices], axis=0)\n    else:\n        avg_slice = volume\n    \n    img = avg_slice.astype(np.float32)\n    if img.max() > img.min():\n        img = (img - img.min()) / (img.max() - img.min())\n    \n    try:\n        thresholds = threshold_multiotsu(img, classes=3)\n        binary = img > thresholds[1]\n    except:\n        thresh = threshold_otsu(img)\n        binary = img > thresh\n    \n    binary = remove_small_objects(binary, min_size=50)\n    binary = remove_small_holes(binary, area_threshold=100)\n    \n    horizontal_kernel = np.ones((1, 15))\n    lines = binary_dilation(binary, footprint=horizontal_kernel)\n    \n    vertical_kernel = np.ones((3, 1))\n    lines = binary_erosion(lines, footprint=vertical_kernel)\n    \n    lines = remove_small_objects(lines, min_size=200)\n    \n    skeleton = skeletonize(lines)\n    \n    labeled_lines = label(skeleton, connectivity=2)\n    \n    mask = np.zeros_like(binary, dtype=bool)\n    for line_id in range(1, labeled_lines.max() + 1):\n        line_mask = labeled_lines == line_id\n        dilated_line = binary_dilation(line_mask, footprint=disk(3))\n        mask = mask | dilated_line\n    \n    if len(volume.shape) == 3:\n        final_mask = np.zeros_like(volume, dtype=bool)\n        \n        for z in range(volume.shape[0]):\n            slice_2d = volume[z]\n            \n            if mask.any():\n                masked_region = slice_2d[mask]\n                if len(masked_region) > 0:\n                    try:\n                        thresh_local = threshold_otsu(masked_region)\n                        slice_binary = (slice_2d > thresh_local) & mask\n                    except:\n                        slice_binary = (slice_2d > np.median(masked_region)) & mask\n                else:\n                    slice_binary = np.zeros_like(slice_2d, dtype=bool)\n            else:\n                thresh_global = threshold_otsu(slice_2d)\n                slice_binary = slice_2d > thresh_global\n            \n            slice_binary = remove_small_objects(slice_binary, min_size=30)\n            final_mask[z] = slice_binary\n        \n        return final_mask\n    \n    return mask\n\ndef document_specific_cleanup(mask):\n    \"\"\"\n    Cleanup specifically for document/text line segmentation\n    \"\"\"\n    mask = remove_small_objects(mask, min_size=100, connectivity=3)\n    \n    selem = rectangle(1, 5)\n    mask = binary_closing(mask, footprint=selem)\n    \n    mask = remove_small_holes(mask, area_threshold=50)\n    \n    selem_vertical = rectangle(3, 1)\n    mask = ndimage.binary_erosion(mask, structure=selem_vertical)\n    mask = ndimage.binary_dilation(mask, structure=selem_vertical)\n    \n    return mask\n\ndef create_submission():\n    \"\"\"Main submission - Modified for document line segmentation\"\"\"\n    print(\"=\"*80)\n    print(\"VESUVIUS CHALLENGE - DOCUMENT LINE SEGMENTATION\")\n    print(\"=\"*80)\n\n    if not TEST_DIR.exists():\n        print(f\"Test images directory not found: {TEST_DIR}\")\n        print(\"Looking for test images in alternative locations...\")\n        \n        test_images = list(BASE_DIR.glob(\"*.tif\")) + list(BASE_DIR.rglob(\"*.tif\"))\n        test_images = [f for f in test_images if \"test\" in str(f).lower()]\n        \n        if not test_images:\n            print(\"No test images found!\")\n            return\n        \n        print(f\"Found {len(test_images)} test images\")\n    else:\n        test_images = list(TEST_DIR.glob(\"*.tif\"))\n        print(f\"Found {len(test_images)} test images in {TEST_DIR}\")\n\n    with zipfile.ZipFile(OUTPUT_ZIP, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zf:\n        for volume_path in tqdm(test_images, desc=\"Processing document volumes\"):\n            volume_id = volume_path.stem\n            filename = f\"{volume_id}.tif\"\n\n            try:\n                print(f\"\\nLoading {filename}...\")\n                volume = load_volume(volume_path)\n\n                print(f\"  Shape: {volume.shape}, dtype: {volume.dtype}\")\n\n                print(\"  Segmenting text lines...\")\n                \n                try:\n                    mask = segment_text_lines(volume)\n                    mask = document_specific_cleanup(mask)\n                except Exception as e1:\n                    print(f\"  Primary method failed: {e1}\")\n                    try:\n                        thresh = threshold_otsu(volume)\n                        mask = volume > thresh\n                        mask = remove_small_objects(mask, min_size=1000)\n                    except:\n                        mask = volume > np.median(volume)\n\n                mask = mask.astype(np.uint8) * 255\n\n                print(f\"  Mask: shape={mask.shape}, dtype={mask.dtype}\")\n\n                temp_path = f\"temp_{volume_id}.tif\"\n                tifffile.imwrite(temp_path, mask)\n\n                with open(temp_path, 'rb') as f:\n                    zf.writestr(filename, f.read())\n\n                os.remove(temp_path)\n\n                print(f\"  ✓ Successfully segmented {filename}\")\n\n            except Exception as e:\n                print(f\"\\n❌ Error processing {filename}: {e}\")\n                import traceback\n                traceback.print_exc()\n                continue\n\n    print(\"\\n\" + \"=\"*80)\n    print(f\"\\n✓ Submission saved: {OUTPUT_ZIP}\")\n    print(\"=\"*80)\n\n# ============================================================================\n# EXECUTE\n# ============================================================================\n\nif __name__ == \"__main__\":\n    create_submission()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-02T07:16:04.748068Z","iopub.execute_input":"2025-12-02T07:16:04.748422Z","iopub.status.idle":"2025-12-02T07:16:15.281120Z","shell.execute_reply.started":"2025-12-02T07:16:04.748402Z","shell.execute_reply":"2025-12-02T07:16:15.280201Z"}},"outputs":[],"execution_count":null}]}