{"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":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 25px 0;\n    box-shadow: \n        0 0 30px rgba(255, 68, 68, 0.5),\n        inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n    line-height: 1.6;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    color: #ff6b6b;\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Simple Submission for the Vesuvius Challenge\n</h1>\n\n<h4 style=\"margin: 12px 0; position: relative; z-index: 1;\">\n    This notebook provides a simple and reliable baseline submission for competition. It generates masks for all test volumes, the inference time is +-2 hours.\n</h4>\n\n<h3 style=\"margin: 15px 0 0 0; font-style: italic; position: relative; z-index: 1;\">\n    This code is not just a solution, but a <strong>starting point for research</strong>.  \n    I leave the detailed analysis and EDA to other people.\n</h3>","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport plotly.graph_objects as go\n\nfrom PIL import Image\nfrom numpy import gradient\n\n# plotly visualize\nimport plotly.offline as pyo\npyo.init_notebook_mode(connected=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:55:56.929318Z","iopub.execute_input":"2025-11-14T07:55:56.930288Z","iopub.status.idle":"2025-11-14T07:55:56.936234Z","shell.execute_reply.started":"2025-11-14T07:55:56.930248Z","shell.execute_reply":"2025-11-14T07:55:56.934953Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Three-dimensional representation of the internal structure of papyrus\n</h1>","metadata":{}},{"cell_type":"code","source":"DATA_ROOT = \"/kaggle/input/vesuvius-challenge-surface-detection\"\nsample_id = \"1004283650\"\n\ndef read_tiff_as_array(tiff_path):\n    \"\"\"Reads a multipage TIFF as a numpy array (D, H, W)\"\"\"\n    img = Image.open(tiff_path)\n    slices = []\n    \n    for i in range(img.n_frames):\n        img.seek(i)\n        frame = np.array(img)\n        slices.append(frame)\n    \n    return np.stack(slices, axis=0)\n\nvolume = read_tiff_as_array(os.path.join(DATA_ROOT, \"train_images\", f\"{sample_id}.tif\"))\nlabel = read_tiff_as_array(os.path.join(DATA_ROOT, \"train_labels\", f\"{sample_id}.tif\"))\n\nsub = 6\nvol_simple = volume[::sub, ::sub, ::sub].astype(np.float32)\nlabel_simple = label[::sub, ::sub, ::sub]\n\nvol_norm = (vol_simple - vol_simple.min()) / (vol_simple.max() - vol_simple.min())\nthreshold = np.percentile(vol_norm, 95)\n\nZ, Y, X = vol_norm.shape\nz_grid, y_grid, x_grid = np.mgrid[0:Z, 0:Y, 0:X]\n\nfig = go.Figure(data=go.Isosurface(\n    x=x_grid.flatten(),\n    y=y_grid.flatten(),\n    z=z_grid.flatten(),\n    value=vol_norm.flatten(),\n    isomin=threshold,\n    isomax=vol_norm.max(),\n    surface_count=1,\n    colorscale='rainbow',\n    opacity=0.55,\n    caps=dict(x_show=False, y_show=False, z_show=False)\n))\n\nfig.update_layout(\n    title=f\"Lightweight 3D Scan {sub}x: 1004283650\",\n    scene=dict(\n        xaxis_title='X',\n        yaxis_title='Y',\n        zaxis_title='Z',\n        aspectmode='data'\n    ),\n    width=750, height=700,\n    template='plotly_dark'\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:55:57.841356Z","iopub.execute_input":"2025-11-14T07:55:57.841688Z","iopub.status.idle":"2025-11-14T07:56:00.571693Z","shell.execute_reply.started":"2025-11-14T07:55:57.841661Z","shell.execute_reply":"2025-11-14T07:56:00.569983Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Reconstruction of papyrus surface geometry\n</h1>","metadata":{}},{"cell_type":"code","source":"z, y, x = np.where(label_simple == 1)\n\nfig = go.Figure(data=go.Scatter3d(\n    x=x, y=y, z=z,\n    mode='markers',\n    marker=dict(\n        size=2.5,\n        color='red',\n        opacity=0.85\n    )\n))\n\nfig.update_layout(\n    title=f\"Simplified Papyrus Surface: 1004283650\",\n    scene=dict(\n        xaxis_title='X',\n        yaxis_title='Y',\n        zaxis_title='Z',\n        aspectmode='data'\n    ),\n    width=750, height=700,\n    template='plotly_dark'\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:39:33.567832Z","iopub.execute_input":"2025-11-14T07:39:33.568164Z","iopub.status.idle":"2025-11-14T07:39:33.615778Z","shell.execute_reply.started":"2025-11-14T07:39:33.568141Z","shell.execute_reply":"2025-11-14T07:39:33.614839Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Papyrus in volume: scan + surface\n</h1>","metadata":{}},{"cell_type":"code","source":"fig = go.Figure()\n\nfig.add_trace(go.Isosurface(\n    x=x_grid.flatten(),\n    y=y_grid.flatten(),\n    z=z_grid.flatten(),\n    value=vol_norm.flatten(),\n    isomin=threshold,\n    isomax=vol_norm.max(),\n    colorscale='hsv',\n    opacity=0.3\n))\n\nfig.add_trace(go.Scatter3d(\n    x=x, y=y, z=z,\n    mode='markers',\n    marker=dict(\n        size=2,\n        color=z,\n        colorscale='Plasma',\n        opacity=0.9\n    ),\n    name='Papyrus Layer'\n))\n\nfig.update_layout(\n    title=f\"Scan + Surface {sub}x\",\n    scene=dict(aspectmode='data'),\n    width=800, height=700,\n    template='plotly_dark'\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:41:13.392478Z","iopub.execute_input":"2025-11-14T07:41:13.392834Z","iopub.status.idle":"2025-11-14T07:41:13.466360Z","shell.execute_reply.started":"2025-11-14T07:41:13.392811Z","shell.execute_reply":"2025-11-14T07:41:13.465185Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    3D reconstruction of the median line of the scroll texture\n</h1>","metadata":{}},{"cell_type":"code","source":"centroids = []\nfor z in range(label.shape[0]):\n    mask = (label[z] == 1)\n    if np.any(mask):\n        y_coords, x_coords = np.where(mask)\n        centroids.append([z, y_coords.mean(), x_coords.mean()])\n\ncentroids = np.array(centroids)\n\nfig = go.Figure(data=go.Scatter3d(\n    x=centroids[:, 2],\n    y=centroids[:, 1],\n    z=centroids[:, 0],\n    mode='lines+markers',\n    line=dict(color='gold', width=6),\n    marker=dict(size=4, color='#FFD700', symbol='circle')\n))\n\nfig.update_layout(\n    title=\"3D Trajectory of Papyrus Layer (Centerline)\",\n    scene=dict(\n        xaxis_title='X (width)',\n        yaxis_title='Y (height)',\n        zaxis_title='Z (depth)',\n        aspectmode='data'\n    ),\n    width=800, height=700,\n    template='plotly_dark'\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:43:15.968754Z","iopub.execute_input":"2025-11-14T07:43:15.969072Z","iopub.status.idle":"2025-11-14T07:43:16.209046Z","shell.execute_reply.started":"2025-11-14T07:43:15.969051Z","shell.execute_reply":"2025-11-14T07:43:16.207960Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Distribution of signal and segmentation density by scan depth\n</h1>","metadata":{}},{"cell_type":"code","source":"signal_profile = np.mean(volume, axis=(1, 2)) # signal: average intensity per layer\nmask_profile = np.mean((label == 1).astype(float), axis=(1, 2)) # mask: the percentage of segmentation pixels per layer\n\n# normalize for comparison\nsignal_norm = (signal_profile - signal_profile.min()) / (signal_profile.max() - signal_profile.min())\nmask_norm = mask_profile / mask_profile.max()\n\nplt.figure(figsize=(12, 5))\nplt.plot(signal_norm, label='Normalized CT Signal', color='gray', linewidth=2)\nplt.fill_between(range(len(mask_norm)), mask_norm, color='red', alpha=0.4, label='Segmentation Density')\n\nplt.title(\"Signal vs Segmentation Density Along Depth (Z-axis)\")\nplt.xlabel(\"Slice Index (Z)\")\nplt.ylabel(\"Normalized Intensity / Density\")\n\nplt.legend()\nplt.grid(True, linestyle='--', alpha=0.6)\nplt.tight_layout()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:43:26.249004Z","iopub.execute_input":"2025-11-14T07:43:26.249907Z","iopub.status.idle":"2025-11-14T07:43:26.784656Z","shell.execute_reply.started":"2025-11-14T07:43:26.249872Z","shell.execute_reply":"2025-11-14T07:43:26.783623Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    The gradient of the papyrus structure and mask in cross section\n</h1>","metadata":{}},{"cell_type":"code","source":"z_mid = volume.shape[0] // 2\nimg_slice = volume[z_mid].astype(float)\nmask_slice = (label[z_mid] == 1)\n\ngrad_y, grad_x = gradient(img_slice)\nedge_strength = np.hypot(grad_x, grad_y)\n\nimg_norm = (img_slice - img_slice.min()) / (img_slice.max() - img_slice.min())\nedge_norm = (edge_strength - edge_strength.min()) / (edge_strength.max() - edge_strength.min())\n\nfig = go.Figure()\n\nfig.add_trace(go.Heatmap(\n    z=edge_norm,\n    colorscale='Blues',\n    showscale=False,\n    name='Structure'\n))\n\ny_mask, x_mask = np.where(mask_slice)\nfig.add_trace(go.Scatter(\n    x=x_mask, y=y_mask,\n    mode='markers',\n    marker=dict(color='red', size=1.2, opacity=0.7),\n    name='Papyrus'\n))\n\nfig.update_layout(\n    title=f\"Structural Edges + Papyrus Mask (Z={z_mid})\",\n    xaxis=dict(scaleanchor=\"y\", scaleratio=1),\n    yaxis=dict(autorange=\"reversed\"),\n    width=700, height=700,\n    showlegend=True,\n    template='plotly_dark'\n)\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:43:30.293210Z","iopub.execute_input":"2025-11-14T07:43:30.293617Z","iopub.status.idle":"2025-11-14T07:43:30.385968Z","shell.execute_reply.started":"2025-11-14T07:43:30.293587Z","shell.execute_reply":"2025-11-14T07:43:30.384484Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<div style=\"\n    background: linear-gradient(135deg, #400000 0%, #661111 50%, #8B0000 100%);\n    border: 2px solid #ff4444;\n    border-radius: 15px;\n    padding: 25px;\n    margin: 20px 0;\n    box-shadow: 0 0 30px rgba(255, 68, 68, 0.5),\n                inset 0 0 20px rgba(255, 255, 255, 0.1);\n    color: #f8f8f8;\n    font-family: 'Segoe UI', system-ui, sans-serif;\n    position: relative;\n    overflow: hidden;\n\">\n\n<div style=\"\n    position: absolute;\n    top: -20px;\n    right: -20px;\n    width: 100px;\n    height: 100px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.3) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<div style=\"\n    position: absolute;\n    bottom: -40px;\n    left: -40px;\n    width: 120px;\n    height: 120px;\n    background: radial-gradient(circle, rgba(255, 68, 68, 0.25) 0%, transparent 70%);\n    border-radius: 50%;\n\"></div>\n\n<h1 style=\"\n    margin: 0 0 20px 0;\n    text-align: center;\n    font-weight: 700;\n    font-size: 1.8em;\n    text-shadow: 0 0 20px rgba(255, 107, 107, 0.7);\n    position: relative;\n    z-index: 1;\n\">\n    Simple submission\n</h1>","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\ntest_csv_path = \"/kaggle/input/vesuvius-challenge-surface-detection/test.csv\"\ntest_images_dir = \"/kaggle/input/vesuvius-challenge-surface-detection/test_images\"\n\ntest_df = pd.read_csv(test_csv_path)\ntest_ids = test_df['id'].tolist()\n\ndef read_tiff_as_array(tiff_path):\n    \"\"\"Reads a multipage TIFF as a numpy array (D, H, W)\"\"\"\n    img = Image.open(tiff_path)\n    slices = []\n    \n    for i in range(img.n_frames):\n        img.seek(i)\n        frame = np.array(img)\n        slices.append(frame)\n    \n    return np.stack(slices, axis=0)\n\ndef write_array_as_tiff(arr, out_path):\n    \"\"\"Saves a 3D numpy array as a multi-page TIFF\"\"\"\n    arr = arr.astype(np.uint8)\n    images = [Image.fromarray(arr[i], mode='L') for i in range(arr.shape[0])]\n    images[0].save(\n        out_path,\n        save_all=True,\n        append_images=images[1:],\n        compression=None,\n    )\n\ndef create_mask(volume):\n    depth, height, width = volume.shape\n    \n    mask = np.zeros((depth, height, width), dtype=np.uint8)\n    \n    depth_range = max(3, depth // 4)\n    d1, d2 = depth // 2 - depth_range, depth // 2 + depth_range\n    \n    size_h = max(1, height // 2)\n    size_w = max(1, width // 2)\n    \n    h1, h2 = height // 2 - size_h, height // 2 + size_h\n    w1, w2 = width // 2 - size_w, width // 2 + size_w\n    \n    d1, d2 = max(0, d1), min(depth, d2)\n    h1, h2 = max(0, h1), min(height, h2)\n    w1, w2 = max(0, w1), min(width, w2)\n    \n    mask[d1:d2, h1:h2, w1:w2] = 1\n    \n    return mask\n\nfor test_id in test_ids:\n    image_path = os.path.join(test_images_dir, f\"{test_id}.tif\")\n    \n    volume = read_tiff_as_array(image_path)\n    \n    mask = create_mask(volume)\n    \n    write_array_as_tiff(mask, f\"{test_id}.tif\")\n\nwith zipfile.ZipFile('submission.zip', 'w') as zipf:\n    for test_id in test_ids:\n        zipf.write(f\"{test_id}.tif\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-14T06:34:27.950292Z","iopub.execute_input":"2025-11-14T06:34:27.950686Z","iopub.status.idle":"2025-11-14T06:34:29.786823Z","shell.execute_reply.started":"2025-11-14T06:34:27.950660Z","shell.execute_reply":"2025-11-14T06:34:29.786007Z"}},"outputs":[],"execution_count":null}]}