{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":117682,"databundleVersionId":15062069,"sourceType":"competition"},{"sourceId":14245247,"sourceType":"datasetVersion","datasetId":9088503},{"sourceId":14295835,"sourceType":"datasetVersion","datasetId":9125518}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import subprocess\nimport os\nimport sys\n\n# 1. СУПЕР-УСТАНОВКА (Фикс всех багов кодеков 2026)\nvar = \"/kaggle/input/vesuvius25-packages-offline-installer-v20251226/whls\"\nsubprocess.run([\n    \"pip\", \"install\", \"--quiet\", \"--no-index\", \"--user\", \"--find-links\", var,\n    f\"{var}/imagecodecs-2025.11.11-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl\",\n    f\"{var}/tifffile-2025.10.16-py3-none-any.whl\",\n    f\"{var}/keras_nightly-3.12.0.dev2025100703-py3-none-any.whl\",\n    f\"{var}/medicai-0.0.3-py3-none-any.whl\"\n], check=False)\n\nimport site\nfrom importlib import reload\nreload(site)\nsys.path.insert(0, site.getusersitepackages())\n\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\nimport keras\nimport numpy as np\nimport pandas as pd\nimport zipfile\nimport tifffile\nimport imagecodecs\nimport scipy.ndimage as ndi\nfrom skimage.morphology import remove_small_objects\n\n# --- ГЛОБАЛЬНЫЙ ВАЙБ-КОНФИГ ---\nMODEL_PATH = \"/kaggle/input/colab-a-162v4-gpu-transunet-seresnext101-x160/model.weights.h5\"\nOVERLAP = 0.6  # Баланс между точностью и лимитом 9 часов\nUSE_8X_TTA = True \n\n# --- БРОНЕБОЙНЫЙ ЗАГРУЗЧИК ---\ndef load_volume_v5(path):\n    with open(path, 'rb') as f:\n        data = f.read()\n    decoded = imagecodecs.imread(data)\n    # Игнорируем метаданные, если они пришли кортежем\n    vol = decoded[0] if isinstance(decoded, (tuple, list)) else decoded\n    orig_shape = vol.shape\n    vol = vol.astype(np.float32)\n    # Робастная нормализация\n    v_min, v_max = np.percentile(vol, [1, 99])\n    vol = np.clip((vol - v_min) / (v_max - v_min + 1e-5), 0, 1)\n    return vol[None, ..., None], orig_shape\n\n# --- МОДЕЛЬ ---\nfrom medicai.models import TransUNet\nfrom medicai.utils.inference import SlidingWindowInference\n\nmodel = TransUNet(input_shape=(160, 160, 160, 1), encoder_name='seresnext101', num_classes=3)\nmodel.compute_output_shape(input_shape=(1, 160, 160, 160, 1))\nif os.path.exists(MODEL_PATH):\n    model.load_weights(MODEL_PATH, skip_mismatch=True)\n\npred_fn = SlidingWindowInference(model, roi_size=(160, 160, 160), num_classes=3, mode=\"gaussian\", overlap=OVERLAP, sw_batch_size=1)\n\n# --- АНСАМБЛЬ TTA (XY + Z FLIPS) ---\ndef predict_tta_v5(sample):\n    probs_accum = []\n    # 4 ротации + флип по горизонтали (X) + флип по глубине (Z)\n    for flip_z in [False, True]:\n        s_z = np.flip(sample, axis=2) if flip_z else sample\n        for flip_x in [False, True]:\n            s_x = np.flip(s_z, axis=3) if flip_x else s_z\n            for k in [0, 1, 2, 3]:\n                s_rot = np.rot90(s_x, k=-k, axes=(2, 3))\n                out = pred_fn(s_rot)\n                p = np.asarray(out)[0, ..., 1]\n                # Разворачиваем обратно\n                p = np.rot90(p, k=k, axes=(1, 2))\n                if flip_x: p = np.flip(p, axis=1)\n                if flip_z: p = np.flip(p, axis=0)\n                probs_accum.append(p)\n    return np.mean(probs_accum, axis=0)\n\n# --- АНСАМБЛЬ ЭВРИСТИК (МАТРЕШКА) ---\ndef heuristic_ensemble(probs):\n    def apply_logic(p, t_low, t_high, close_rad):\n        mask = (p >= t_high)\n        mask = ndi.binary_propagation(mask, mask=(p >= t_low))\n        # Склеиваем разрывы\n        if close_rad > 0:\n            mask = ndi.binary_closing(mask, structure=np.ones((close_rad, close_rad, close_rad)))\n        # Заполняем внутренние пустоты папируса\n        mask = ndi.binary_fill_holes(mask)\n        return mask\n\n    # Под-ансамбль 1: Агрессивный (ловит тонкие слои)\n    m1 = apply_logic(probs, 0.35, 0.70, close_rad=1)\n    # Под-ансамбль 2: Баланс\n    m2 = apply_logic(probs, 0.45, 0.80, close_rad=2)\n    # Под-ансамбль 3: Консервативный (минимум шума)\n    m3 = apply_logic(probs, 0.55, 0.85, close_rad=1)\n\n    # Голосование: берем воксель, если за него хотя бы 2 под-ансамбля\n    ensemble = ( (m1.astype(np.uint8) + m2.astype(np.uint8) + m3.astype(np.uint8)) >= 2 )\n    \n    # Финальная чистка\n    ensemble = remove_small_objects(ensemble, min_size=800)\n    labels, num = ndi.label(ensemble)\n    if num > 0:\n        counts = np.bincount(labels.flat)\n        counts[0] = 0\n        ensemble = (labels == np.argmax(counts))\n    \n    return ensemble.astype(np.uint8)\n\n# --- ОСНОВНОЙ ЦИКЛ ---\ntest_df = pd.read_csv(\"/kaggle/input/vesuvius-challenge-surface-detection/test.csv\")\nwith zipfile.ZipFile(\"submission.zip\", \"w\") as z:\n    for image_id in test_df[\"id\"]:\n        path = f\"/kaggle/input/vesuvius-challenge-surface-detection/test_images/{image_id}.tif\"\n        print(f\"🌲 V5 Yggdrasil Processing: {image_id}\")\n        try:\n            vol, orig_shape = load_volume_v5(path)\n            \n            # Предсказание с расширенным TTA\n            probs = predict_tta_v5(vol)\n            \n            # Ансамбль геометрических эвристик\n            mask = heuristic_ensemble(probs)\n            \n            # Приведение к оригинальному размеру\n            mask = np.squeeze(mask)\n            if mask.shape != orig_shape:\n                mask = mask[:orig_shape[0], :orig_shape[1], :orig_shape[2]]\n            \n            out_name = f\"{image_id}.tif\"\n            tifffile.imwrite(out_name, mask.astype(np.uint8), photometric='minisblack', compression=None)\n            z.write(out_name)\n            os.remove(out_name)\n            print(f\"✅ Created: {out_name}\")\n        except Exception as e:\n            print(f\"❌ Critical Error on {image_id}: {e}\")\n\nprint(\"🏆 V5 Yggdrasil finished. Ready for Top-1.\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-25T05:52:30.458964Z","iopub.execute_input":"2026-01-25T05:52:30.459557Z","iopub.status.idle":"2026-01-25T05:52:43.145407Z","shell.execute_reply.started":"2026-01-25T05:52:30.459530Z","shell.execute_reply":"2026-01-25T05:52:43.144712Z"}},"outputs":[],"execution_count":null}]}