{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":117682,"databundleVersionId":14443416,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# The `medicai` is medical-based 2D and 3D ML library. \n# We'll use it for segmentaiton model, 3D volume transformation, etc.\n!pip install git+https://github.com/innat/medic-ai.git -q\n!pip install imagecodecs -q","metadata":{"execution":{"iopub.status.busy":"2025-11-14T00:51:52.031004Z","iopub.execute_input":"2025-11-14T00:51:52.031669Z","iopub.status.idle":"2025-11-14T00:52:08.272567Z","shell.execute_reply.started":"2025-11-14T00:51:52.031632Z","shell.execute_reply":"2025-11-14T00:52:08.271845Z"}}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os, warnings\n\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nwarnings.filterwarnings('ignore')\n\nimport tensorflow as tf\nimport keras\nfrom keras import ops\n\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport tifffile","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:48:02.575337Z","iopub.execute_input":"2025-11-14T07:48:02.575551Z","iopub.status.idle":"2025-11-14T07:48:21.763634Z","shell.execute_reply.started":"2025-11-14T07:48:02.575533Z","shell.execute_reply":"2025-11-14T07:48:21.762996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"keras.version(), keras.config.backend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:48:30.351169Z","iopub.execute_input":"2025-11-14T07:48:30.351464Z","iopub.status.idle":"2025-11-14T07:48:30.357247Z","shell.execute_reply.started":"2025-11-14T07:48:30.351442Z","shell.execute_reply":"2025-11-14T07:48:30.35653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"root_dir = \"/kaggle/input/vesuvius-challenge-surface-detection\"\nimages_dir = f\"{root_dir}/train_images\"\nlabels_dir = f\"{root_dir}/train_labels\"\nall_image_files = sorted(tf.io.gfile.glob(os.path.join(images_dir, \"*.tif\")))\nall_label_files = sorted(tf.io.gfile.glob(os.path.join(labels_dir, \"*.tif\")))\n\ntrain_imgs, val_imgs, train_lbls, val_lbls = train_test_split(\n    all_image_files,\n    all_label_files,\n    test_size=5,\n    random_state=42,\n    shuffle=True,\n)\n\nprint(\"Train images:\", len(train_imgs), len(train_lbls))\nprint(\"Val images:  \", len(val_imgs), len(val_lbls))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:48:35.701181Z","iopub.execute_input":"2025-11-14T07:48:35.701944Z","iopub.status.idle":"2025-11-14T07:48:36.262079Z","shell.execute_reply.started":"2025-11-14T07:48:35.701918Z","shell.execute_reply":"2025-11-14T07:48:36.261288Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/vesuvius-challenge-surface-detection/train.csv')\nprint(df.id.nunique(), df.scroll_id.nunique())\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:48:43.865686Z","iopub.execute_input":"2025-11-14T07:48:43.865986Z","iopub.status.idle":"2025-11-14T07:48:43.901487Z","shell.execute_reply.started":"2025-11-14T07:48:43.865966Z","shell.execute_reply":"2025-11-14T07:48:43.9009Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Quick Look**","metadata":{}},{"cell_type":"code","source":"for path in train_imgs:\n    img = Image.open(path).convert('RGB')\n    arr = np.array(img)\n    print(\"Shape:\", arr.shape)\n    print(\"Dtype:\", arr.dtype)\n    print(\"Min:\", arr.min())\n    print(\"Max:\", arr.max())\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:48:54.112202Z","iopub.execute_input":"2025-11-14T07:48:54.112783Z","iopub.status.idle":"2025-11-14T07:48:54.334921Z","shell.execute_reply.started":"2025-11-14T07:48:54.112759Z","shell.execute_reply":"2025-11-14T07:48:54.334189Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for path in train_lbls:\n    img = Image.open(path).convert('RGB')\n    arr = np.array(img)\n    print(\"Shape:\", arr.shape)\n    print(\"Dtype:\", arr.dtype)\n    print(\"Min:\", arr.min())\n    print(\"Max:\", arr.max())\n    print(\"Unique values:\", np.unique(arr))  # show first 20 unique values\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:00.715493Z","iopub.execute_input":"2025-11-14T07:49:00.716081Z","iopub.status.idle":"2025-11-14T07:49:00.735823Z","shell.execute_reply.started":"2025-11-14T07:49:00.716055Z","shell.execute_reply":"2025-11-14T07:49:00.735254Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data Loader","metadata":{}},{"cell_type":"code","source":"from medicai.transforms import (\n    Compose,\n    NormalizeIntensity,\n    Resize,\n    RandShiftIntensity,\n    RandRotate90,\n    RandFlip,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:07.558162Z","iopub.execute_input":"2025-11-14T07:49:07.558458Z","iopub.status.idle":"2025-11-14T07:49:07.848828Z","shell.execute_reply.started":"2025-11-14T07:49:07.558437Z","shell.execute_reply":"2025-11-14T07:49:07.84784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_transformation(image, label):\n    data = {\"image\": image, \"label\": label}\n    pipeline = Compose([\n        NormalizeIntensity(\n            keys=[\"image\"],\n            nonzero=True,\n            channel_wise=True\n        ),\n        Resize(\n            keys=[\"image\", \"label\"],\n            spatial_shape=(64, 128, 128),\n            mode=(\"trilinear\", \"nearest\")\n        ),\n        RandFlip(keys=[\"image\", \"label\"], spatial_axis=[0], prob=0.5),\n        RandFlip(keys=[\"image\", \"label\"], spatial_axis=[1], prob=0.5),\n        RandFlip(keys=[\"image\", \"label\"], spatial_axis=[2], prob=0.5),\n        RandShiftIntensity(\n            keys=[\"image\"],\n            offsets=0.10,\n            prob=1.0\n        )\n    ])\n    result = pipeline(data)\n    return result[\"image\"], result[\"label\"]\n\n\ndef val_transformation(image, label):\n    data = {\"image\": image, \"label\": label}\n    pipeline = Compose([\n        NormalizeIntensity(\n            keys=[\"image\"],\n            nonzero=True,\n            channel_wise=True\n        ),\n    ])\n    result = pipeline(data)\n    return result[\"image\"], result[\"label\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:13.658616Z","iopub.execute_input":"2025-11-14T07:49:13.658895Z","iopub.status.idle":"2025-11-14T07:49:13.66557Z","shell.execute_reply.started":"2025-11-14T07:49:13.658873Z","shell.execute_reply":"2025-11-14T07:49:13.664873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DataLoader(keras.utils.Sequence):\n    def __init__(\n        self, \n        image_paths, \n        labels,\n        batch_size=1, \n        dim=(128, 128, 128), \n        shuffle=True, \n        training=True,\n        **kwargs\n    ):\n        super().__init__(**kwargs)\n        self.image_paths = image_paths\n        self.labels = labels\n        self.batch_size = batch_size\n        self.dim = dim  # (D, H, W)\n        self.shuffle = shuffle\n        self.training = training\n        self.on_epoch_end()\n\n    def __len__(self):\n        return int(np.floor(len(self.image_paths) / self.batch_size))\n\n    def __getitem__(self, index):\n        # Generate batch indices\n        indices = self.indices[index * self.batch_size : (index + 1) * self.batch_size]\n        image_paths_batch = [self.image_paths[k] for k in indices]\n        labels_batch = [self.labels[k] for k in indices]\n\n        # Initialize arrays\n        X = []\n        y = []\n\n        # Load and preprocess batch\n        for i, (img_path, label_path) in enumerate(zip(image_paths_batch, labels_batch)):\n            image = tifffile.imread(img_path) # shape: (D, H, W)\n            label = tifffile.imread(label_path)\n\n            # Add channel dimension if neede\n            image = np.expand_dims(image, axis=-1)  # (D, H, W, 1)\n            label = np.expand_dims(label, axis=-1)  # (D, H, W, 1)\n\n            image = image.astype(np.float32)\n            label = label.astype(np.float32)\n\n            # Apply transformations\n            if self.training:\n                image, label = train_transformation(image, label)\n            else:\n                image, label = val_transformation(image, label)\n\n            X.append(image)\n            y.append(label)\n\n        X = np.stack(X, axis=0)\n        y = np.stack(y, axis=0)\n        return X, y\n\n    def on_epoch_end(self):\n        # Shuffle indices after each epoch\n        self.indices = np.arange(len(self.image_paths))\n        if self.shuffle:\n            np.random.shuffle(self.indices)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:20.980683Z","iopub.execute_input":"2025-11-14T07:49:20.9814Z","iopub.status.idle":"2025-11-14T07:49:20.989383Z","shell.execute_reply.started":"2025-11-14T07:49:20.981373Z","shell.execute_reply":"2025-11-14T07:49:20.988576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"input_shape=(64, 128, 128)\nbatch_size=6\n\ntrain_loader = DataLoader(\n    image_paths=train_imgs,\n    labels=train_lbls,\n    batch_size=batch_size,\n    dim=input_shape,\n    shuffle=True,\n    training=True\n)\n\nval_loader = DataLoader(\n    image_paths=val_imgs,\n    labels=val_lbls,\n    batch_size=1,\n    dim=input_shape,\n    shuffle=False,\n    training=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:30.575621Z","iopub.execute_input":"2025-11-14T07:49:30.576296Z","iopub.status.idle":"2025-11-14T07:49:30.580725Z","shell.execute_reply.started":"2025-11-14T07:49:30.576271Z","shell.execute_reply":"2025-11-14T07:49:30.579755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x, y = next(iter(train_loader))\nx.shape, y.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:36.633094Z","iopub.execute_input":"2025-11-14T07:49:36.633629Z","iopub.status.idle":"2025-11-14T07:49:37.154845Z","shell.execute_reply.started":"2025-11-14T07:49:36.633607Z","shell.execute_reply":"2025-11-14T07:49:37.15376Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Viz**","metadata":{}},{"cell_type":"code","source":"def plot_3d_sample(x, y, sample_idx=0, max_slices=16):\n    img = np.squeeze(x[sample_idx])  # (D, H, W)\n    mask = np.squeeze(y[sample_idx])  # (D, H, W)\n    D = img.shape[0]\n\n    # Decide which slices to plot\n    step = max(1, D // max_slices)\n    slices = range(0, D, step)\n\n    n_slices = len(slices)\n    fig, axes = plt.subplots(2, n_slices, figsize=(3*n_slices, 6))\n\n    for i, s in enumerate(slices):\n        axes[0, i].imshow(img[s], cmap='gray')\n        axes[0, i].set_title(f\"Slice {s}\")\n        axes[0, i].axis('off')\n\n        axes[1, i].imshow(mask[s], cmap='gray')\n        axes[1, i].set_title(f\"Mask {s}\")\n        axes[1, i].axis('off')\n\n    plt.suptitle(f\"Sample {sample_idx}\")\n    plt.tight_layout()\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:48.031989Z","iopub.execute_input":"2025-11-14T07:49:48.032677Z","iopub.status.idle":"2025-11-14T07:49:48.038693Z","shell.execute_reply.started":"2025-11-14T07:49:48.032656Z","shell.execute_reply":"2025-11-14T07:49:48.038005Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_3d_sample(x, y, sample_idx=0, max_slices=4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:49:54.111207Z","iopub.execute_input":"2025-11-14T07:49:54.111806Z","iopub.status.idle":"2025-11-14T07:49:54.126025Z","shell.execute_reply.started":"2025-11-14T07:49:54.111784Z","shell.execute_reply":"2025-11-14T07:49:54.12492Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"import medicai\nfrom medicai.models import UNet\nfrom medicai.losses import SparseDiceCELoss\nfrom medicai.metrics import SparseDiceMetric\nfrom medicai.callbacks import SlidingWindowInferenceCallback","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:01.866205Z","iopub.execute_input":"2025-11-14T07:50:01.866889Z","iopub.status.idle":"2025-11-14T07:50:01.882213Z","shell.execute_reply.started":"2025-11-14T07:50:01.866865Z","shell.execute_reply":"2025-11-14T07:50:01.881323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# medicai.models.list_models()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:11.909585Z","iopub.execute_input":"2025-11-14T07:50:11.910456Z","iopub.status.idle":"2025-11-14T07:50:11.913946Z","shell.execute_reply.started":"2025-11-14T07:50:11.91043Z","shell.execute_reply":"2025-11-14T07:50:11.913113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes=3\nmodel = UNet(\n    input_shape=(64, 128, 128, 1),\n    encoder_name='efficientnet_v2_b0',\n    encoder_depth=4,\n    classifier_activation='softmax',\n    num_classes=num_classes,\n)\nmodel.count_params() / 1e6","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:16.386189Z","iopub.execute_input":"2025-11-14T07:50:16.386809Z","iopub.status.idle":"2025-11-14T07:50:16.401481Z","shell.execute_reply.started":"2025-11-14T07:50:16.386788Z","shell.execute_reply":"2025-11-14T07:50:16.400531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# define optomizer, loss, metrics\noptim = keras.optimizers.AdamW(\n    learning_rate=1e-4,\n    weight_decay=1e-5,\n)\n\nloss_fn = SparseDiceCELoss(\n    from_logits=False, \n    num_classes=num_classes\n)\n\nmetrics = [\n    SparseDiceMetric(\n        from_logits=False, \n        num_classes=num_classes, \n        name='dice'\n    ),\n]\n\nmodel.compile(\n    optimizer=optim,\n    loss=loss_fn,\n    metrics=metrics\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:22.157437Z","iopub.execute_input":"2025-11-14T07:50:22.158043Z","iopub.status.idle":"2025-11-14T07:50:23.082318Z","shell.execute_reply.started":"2025-11-14T07:50:22.158019Z","shell.execute_reply":"2025-11-14T07:50:23.081495Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"swi_callback_metric = SparseDiceMetric(\n    from_logits=False,\n    ignore_empty=True,\n    num_classes=num_classes,\n    name='val_dice',\n)\n\nswi_callback = SlidingWindowInferenceCallback(\n    model,\n    dataset=val_loader,\n    metrics=swi_callback_metric,\n    num_classes=num_classes,\n    interval=5,\n    overlap=0.5,\n    roi_size=(64, 128, 128),\n    sw_batch_size=2,\n    save_path=\"model.weights.h5\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:30.441562Z","iopub.execute_input":"2025-11-14T07:50:30.44229Z","iopub.status.idle":"2025-11-14T07:50:30.45792Z","shell.execute_reply.started":"2025-11-14T07:50:30.442265Z","shell.execute_reply":"2025-11-14T07:50:30.45673Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.fit(\n    train_loader,\n    epochs=20,\n    callbacks=[\n        swi_callback\n    ]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-14T07:50:36.820818Z","iopub.execute_input":"2025-11-14T07:50:36.821431Z","iopub.status.idle":"2025-11-14T07:50:36.836003Z","shell.execute_reply.started":"2025-11-14T07:50:36.821408Z","shell.execute_reply":"2025-11-14T07:50:36.835118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}