{"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":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Training Notebooks","metadata":{}},{"cell_type":"code","source":"var=\"/kaggle/input/vsdetection-packages-offline-installer-only/whls\"\n!pip install \\\n    \"$var\"/tifffile-2025.10.16-py3-none-any.whl \\\n    \"$var\"/imagecodecs-2025.11.11-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl \\\n    \"$var\"/medicai-0.0.3-py3-none-any.whl \\\n    --no-index \\\n    --find-links \"$var\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:36:43.58587Z","iopub.execute_input":"2025-12-07T16:36:43.586624Z","iopub.status.idle":"2025-12-07T16:36:48.162401Z","shell.execute_reply.started":"2025-12-07T16:36:43.586595Z","shell.execute_reply":"2025-12-07T16:36:48.161665Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"torch\"\n\nimport keras\nfrom medicai.transforms import (\n    Compose,\n    ScaleIntensityRange,\n)\nimport torch\nfrom medicai.models import SegFormer, TransUNet\nfrom medicai.utils.inference import SlidingWindowInference\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport numpy as np\nimport pandas as pd\nimport zipfile\nimport tensorflow as tf\nimport tifffile\nfrom tqdm import tqdm\nfrom keras import ops\nfrom matplotlib import pyplot as plt\n\nkeras.config.backend(), keras.version()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:39:05.815236Z","iopub.execute_input":"2025-12-07T16:39:05.815562Z","iopub.status.idle":"2025-12-07T16:39:05.822619Z","shell.execute_reply.started":"2025-12-07T16:39:05.815539Z","shell.execute_reply":"2025-12-07T16:39:05.821789Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Transformation**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nfrom medicai.transforms import (\n    Compose,\n    ScaleIntensityRange,\n    RandShiftIntensity,\n    RandRotate90,\n    RandFlip,\n    RandSpatialCrop\n)\n\n# ==================== FIXED CUSTOM TRANSFORMS ====================\n\nclass RandAdjustContrast:\n    \"\"\"Random Contrast Adjustment (Gamma Correction) - TF Graph Compatible\"\"\"\n    def __init__(self, keys, prob=0.3, gamma_range=(0.7, 1.3)):\n        self.keys = keys\n        self.prob = prob\n        self.gamma_range = gamma_range\n    \n    def __call__(self, data):\n        for key in self.keys:\n            # Use tf.cond instead of Python if\n            data[key] = tf.cond(\n                tf.random.uniform([]) < self.prob,\n                lambda: self._apply_gamma(data[key]),\n                lambda: data[key]\n            )\n        return data\n    \n    def _apply_gamma(self, image):\n        gamma = tf.random.uniform(\n            [], \n            self.gamma_range[0], \n            self.gamma_range[1]\n        )\n        # Clip to [0, 1] before applying gamma\n        image_clipped = tf.clip_by_value(image, 0.0, 1.0)\n        return tf.pow(image_clipped, gamma)\n\n\nclass RandUnsharpMask:\n    \"\"\"Random Unsharp Masking - TF Graph Compatible\"\"\"\n    def __init__(self, keys, prob=0.3, amount_range=(0.5, 1.5), kernel_size=3):\n        self.keys = keys\n        self.prob = prob\n        self.amount_range = amount_range\n        self.kernel_size = kernel_size\n    \n    def __call__(self, data):\n        for key in self.keys:\n            data[key] = tf.cond(\n                tf.random.uniform([]) < self.prob,\n                lambda: self._apply_unsharp_mask(data[key]),\n                lambda: data[key]\n            )\n        return data\n    \n    def _apply_unsharp_mask(self, image):\n        amount = tf.random.uniform(\n            [], \n            self.amount_range[0], \n            self.amount_range[1]\n        )\n        \n        # Simple box blur untuk 3D\n        blurred = self._box_blur_3d(image, self.kernel_size)\n        \n        # Unsharp mask formula\n        sharpened = image + amount * (image - blurred)\n        return tf.clip_by_value(sharpened, 0.0, 1.0)\n    \n    def _box_blur_3d(self, volume, kernel_size):\n        \"\"\"Box blur menggunakan average pooling 3D\"\"\"\n        # Add batch dimension\n        volume_batched = volume[tf.newaxis, ...]\n        \n        # Apply 3D average pooling\n        ksize = [1, kernel_size, kernel_size, kernel_size, 1]\n        strides = [1, 1, 1, 1, 1]\n        blurred = tf.nn.avg_pool3d(\n            volume_batched,\n            ksize=ksize,\n            strides=strides,\n            padding='SAME'\n        )\n        \n        # Remove batch dimension\n        return blurred[0]\n\n\nclass RandAddGaussianNoise:\n    \"\"\"Random Gaussian Noise - TF Graph Compatible\"\"\"\n    def __init__(self, keys, prob=0.2, noise_std_range=(0.0, 0.05)):\n        self.keys = keys\n        self.prob = prob\n        self.noise_std_range = noise_std_range\n    \n    def __call__(self, data):\n        for key in self.keys:\n            data[key] = tf.cond(\n                tf.random.uniform([]) < self.prob,\n                lambda: self._add_noise(data[key]),\n                lambda: data[key]\n            )\n        return data\n    \n    def _add_noise(self, image):\n        noise_std = tf.random.uniform(\n            [], \n            self.noise_std_range[0], \n            self.noise_std_range[1]\n        )\n        \n        noise = tf.random.normal(\n            tf.shape(image),\n            mean=0.0,\n            stddev=noise_std\n        )\n        \n        noisy = image + noise\n        return tf.clip_by_value(noisy, 0.0, 1.0)\n\n\nclass RandBrightnessContrast:\n    \"\"\"Random Brightness and Contrast adjustment - TF Graph Compatible\"\"\"\n    def __init__(self, keys, prob=0.3, \n                 brightness_range=(-0.1, 0.1),\n                 contrast_range=(0.8, 1.2)):\n        self.keys = keys\n        self.prob = prob\n        self.brightness_range = brightness_range\n        self.contrast_range = contrast_range\n    \n    def __call__(self, data):\n        for key in self.keys:\n            data[key] = tf.cond(\n                tf.random.uniform([]) < self.prob,\n                lambda: self._adjust_brightness_contrast(data[key]),\n                lambda: data[key]\n            )\n        return data\n    \n    def _adjust_brightness_contrast(self, image):\n        # Random brightness adjustment\n        brightness_delta = tf.random.uniform(\n            [],\n            self.brightness_range[0],\n            self.brightness_range[1]\n        )\n        image = image + brightness_delta\n        \n        # Random contrast adjustment\n        contrast_factor = tf.random.uniform(\n            [],\n            self.contrast_range[0],\n            self.contrast_range[1]\n        )\n        mean = tf.reduce_mean(image)\n        image = (image - mean) * contrast_factor + mean\n        \n        return tf.clip_by_value(image, 0.0, 1.0)\n\n\nclass RandHistogramShift:\n    \"\"\"Random Histogram Shifting - Simplified TF Graph Compatible\"\"\"\n    def __init__(self, keys, prob=0.3, shift_range=(-0.1, 0.1)):\n        self.keys = keys\n        self.prob = prob\n        self.shift_range = shift_range\n    \n    def __call__(self, data):\n        for key in self.keys:\n            data[key] = tf.cond(\n                tf.random.uniform([]) < self.prob,\n                lambda: self._shift_histogram(data[key]),\n                lambda: data[key]\n            )\n        return data\n    \n    def _shift_histogram(self, image):\n        \"\"\"Simplified histogram shift using intensity scaling\"\"\"\n        shift = tf.random.uniform(\n            [],\n            self.shift_range[0],\n            self.shift_range[1]\n        )\n        \n        # Non-linear transformation\n        shifted = image + shift * (image - 0.5)\n        return tf.clip_by_value(shifted, 0.0, 1.0)\n\n\n# ==================== PIPELINE DENGAN FIXED TRANSFORMS ====================\n\ndef train_transformation(image, label):\n    \"\"\"Training transformation dengan custom enhancement - FIXED VERSION\"\"\"\n    data = {\"image\": image, \"label\": label}\n    \n    pipeline = Compose([\n        # 1. Normalisasi dasar\n        ScaleIntensityRange(\n            keys=[\"image\"],\n            a_min=0,\n            a_max=255,\n            clip=True,\n        ),\n        \n        # 2. Spatial augmentation (dari medicai)\n        RandSpatialCrop(\n            keys=[\"image\", \"label\"],\n            roi_size=(128, 128, 128),\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        RandRotate90(\n            keys=[\"image\", \"label\"], \n            prob=0.4, \n            max_k=3, \n            spatial_axes=(0, 1)\n        ),\n        \n        # 3. CUSTOM ENHANCEMENT TRANSFORMS (FIXED)\n        RandAdjustContrast(\n            keys=[\"image\"],\n            prob=0.3,\n            gamma_range=(0.7, 1.3)\n        ),\n        \n        RandUnsharpMask(\n            keys=[\"image\"],\n            prob=0.25,\n            amount_range=(0.5, 1.2),\n            kernel_size=3\n        ),\n        \n        RandBrightnessContrast(\n            keys=[\"image\"],\n            prob=0.3,\n            brightness_range=(-0.1, 0.1),\n            contrast_range=(0.85, 1.15)\n        ),\n        \n        RandAddGaussianNoise(\n            keys=[\"image\"],\n            prob=0.2,\n            noise_std_range=(0.0, 0.03)\n        ),\n        \n        RandHistogramShift(\n            keys=[\"image\"],\n            prob=0.2,\n            shift_range=(-0.05, 0.05)\n        ),\n        \n        # 4. Intensity shift (dari medicai)\n        RandShiftIntensity(\n            keys=[\"image\"], \n            offsets=0.10, \n            prob=0.5\n        ),\n    ])\n    \n    result = pipeline(data)\n    return result[\"image\"], result[\"label\"]\n\n\ndef val_transformation(image, label):\n    \"\"\"Validation transform - hanya normalisasi\"\"\"\n    data = {\"image\": image, \"label\": label}\n    pipeline = Compose([\n        ScaleIntensityRange(\n            keys=[\"image\"],\n            a_min=0,\n            a_max=255,\n            clip=True,\n        ),\n    ])\n    result = pipeline(data)\n    return result[\"image\"], result[\"label\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:05.704755Z","iopub.execute_input":"2025-12-07T16:37:05.705715Z","iopub.status.idle":"2025-12-07T16:37:05.727505Z","shell.execute_reply.started":"2025-12-07T16:37:05.705689Z","shell.execute_reply":"2025-12-07T16:37:05.726817Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Model**","metadata":{}},{"cell_type":"code","source":"import medicai\nfrom medicai.losses import BinaryDiceCELoss\nfrom medicai.metrics import BinaryDiceMetric","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:06.080746Z","iopub.execute_input":"2025-12-07T16:37:06.081386Z","iopub.status.idle":"2025-12-07T16:37:06.089024Z","shell.execute_reply.started":"2025-12-07T16:37:06.081361Z","shell.execute_reply":"2025-12-07T16:37:06.088417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = 1\nmodel = SegFormer(\n    input_shape=(128, 128, 128, 1),\n    encoder_name='mit_b0',\n    classifier_activation='sigmoid',\n    num_classes=num_classes,\n)\nmodel.count_params() / 1e6","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:06.260184Z","iopub.execute_input":"2025-12-07T16:37:06.260435Z","iopub.status.idle":"2025-12-07T16:37:07.741431Z","shell.execute_reply.started":"2025-12-07T16:37:06.260417Z","shell.execute_reply":"2025-12-07T16:37:07.740664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# define optomizer, loss\noptim = keras.optimizers.AdamW(\n    learning_rate=1e-4,\n    weight_decay=1e-5,\n)\nloss_fn = BinaryDiceCELoss(\n    from_logits=False, \n    num_classes=num_classes\n)\n\n# define sliding-window-inferencer for validation\nswi = SlidingWindowInference(\n    model,\n    num_classes=num_classes,\n    roi_size=(128, 128, 128),\n    sw_batch_size=4,\n    overlap=0.5,\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:07.742611Z","iopub.execute_input":"2025-12-07T16:37:07.742833Z","iopub.status.idle":"2025-12-07T16:37:07.764908Z","shell.execute_reply.started":"2025-12-07T16:37:07.742814Z","shell.execute_reply":"2025-12-07T16:37:07.764201Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, dataloader, metrics):\n    loop = tqdm(dataloader, desc=\"Training\", leave=False)\n    \n    for imgs, labels in loop:\n        # forward pass\n        outputs = model(imgs)\n        loss = loss_fn(labels, outputs)\n\n        # backward pass\n        model.zero_grad()\n        trainable_weights = [v for v in model.trainable_weights]\n\n        # call torch.Tensor.backward() on the loss to compute gradients\n        loss.backward()\n        gradients = [v.value.grad for v in trainable_weights]\n\n        # update weights\n        with torch.no_grad():\n            optim.apply(gradients, trainable_weights)\n\n        # update training metric\n        y_true = ops.convert_to_tensor(labels)\n        y_pred = ops.convert_to_tensor(outputs)\n        \n        # Kalau 5D: (B, D, H, W, C) → flatten jadi (B, D*H*W, C)\n        if len(y_true.shape) == 5:\n            b, d, h, w, c = y_true.shape\n            y_true = ops.reshape(y_true, (b, d * h * w, c))\n            y_pred = ops.reshape(y_pred, (b, d * h * w, c))\n        \n        metrics.update_state(y_true, y_pred)\n\n        \n        # Update tqdm\n        loss_score = ops.convert_to_numpy(loss)\n        metrics_score = ops.convert_to_numpy(metrics.result())\n        loop.set_postfix(\n            loss=loss_score,\n            dice=metrics_score,\n        )\n\n    return loss, metrics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:07.765682Z","iopub.execute_input":"2025-12-07T16:37:07.765926Z","iopub.status.idle":"2025-12-07T16:37:07.772471Z","shell.execute_reply.started":"2025-12-07T16:37:07.765905Z","shell.execute_reply":"2025-12-07T16:37:07.771635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(model, dataloader, metrics):\n    # optional tapi bagus: set ke eval\n    model.eval()\n\n    for x, y in dataloader:\n        # kalau pakai torch backend, sebaiknya no_grad biar hemat memori\n        with torch.no_grad():\n            output = swi(x)  # atau model(x), sesuai punyamu\n\n        # konversi ke tensor keras.ops\n        y = ops.convert_to_tensor(y)\n        output = ops.convert_to_tensor(output)\n\n        # EXPECTED SHAPE awal:\n        # y      : (B, D, H, W, C)\n        # output : (B, D, H, W, C)\n        if len(y.shape) == 5:\n            b, d, h, w, c = y.shape  # (batch, depth, height, width, channel)\n\n            # flatten dim spasial → (B, N, C), N = D*H*W\n            y = ops.reshape(y, (b, d * h * w, c))\n            output = ops.reshape(output, (b, d * h * w, c))\n\n        # urutan: (y_true, y_pred)\n        metrics.update_state(y, output)\n\n    return metrics\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:07.773766Z","iopub.execute_input":"2025-12-07T16:37:07.773991Z","iopub.status.idle":"2025-12-07T16:37:07.787789Z","shell.execute_reply.started":"2025-12-07T16:37:07.773976Z","shell.execute_reply":"2025-12-07T16:37:07.787043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_training(train_loader, val_loader, model, epochs=20):\n    # metrics for train\n    train_metrics = BinaryDiceMetric(\n        from_logits=False, \n        num_classes=num_classes, \n        name='dice'\n    )\n\n    # metrics for validation\n    val_metrics = BinaryDiceMetric(\n        from_logits=False, \n        num_classes=num_classes, \n        name='val_dice'\n    )\n    \n    # Initialize best validation dice score\n    best_val_dice = 0.0\n\n    for epoch in range(epochs):\n        print(f'Epoch {epoch+1}/{epochs}')\n        \n        # Training\n        loss, train_metrics = train_one_epoch(\n            model, train_loader, train_metrics,\n        )\n        # display training logs at the end of epoch\n        train_metrics_score = ops.convert_to_numpy(train_metrics.result())\n        loss_score = ops.convert_to_numpy(loss)\n        \n        # reset training metrics at the end of each epoch\n        train_metrics.reset_state()\n\n        # Validation [at every 5 epoch]\n        if (epoch + 1) % 5 == 0:\n            val_metrics = validate(model, val_loader, val_metrics)\n            val_metrics_score = ops.convert_to_numpy(\n                val_metrics.result()\n            )\n            val_metrics.reset_state()\n            print(\n                f'Training - Loss: {loss_score:.4f}, Dice: {train_metrics_score:.4f}'\n                f'\\nValidation - Dice: {val_metrics_score:.4f}\\n'\n            )\n\n            # Save best model weights\n            if val_metrics_score > best_val_dice:\n                best_val_dice = val_metrics_score\n                model.save_weights('model.weights.h5')\n                # torch.save(model.state_dict(), 'model.pth') # OK too.\n                print(\n                    f'Dice score improved: {best_val_dice}. Model saved.'\n                )\n        else:\n            print(\n                f'Training - Loss: {loss_score:.4f}, Dice: {train_metrics_score:.4f}\\n'\n            )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:09.433426Z","iopub.execute_input":"2025-12-07T16:37:09.43371Z","iopub.status.idle":"2025-12-07T16:37:09.440118Z","shell.execute_reply.started":"2025-12-07T16:37:09.43369Z","shell.execute_reply":"2025-12-07T16:37:09.439318Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Sliding Window Inference**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport tifffile\n\ndef _read_tiff(path):\n    # path: tf.string → numpy bytes → decode\n    path = path.decode(\"utf-8\")\n    vol = tifffile.imread(path).astype(np.uint8)  # (D, H, W) 3D volume\n    return vol\n\ndef parse_tiff_fn(image_path, label_path):\n    # Baca image & label dari path dengan numpy_function\n    image = tf.numpy_function(_read_tiff, [image_path], tf.uint8)\n    label = tf.numpy_function(_read_tiff, [label_path], tf.uint8)\n\n    # Set shape ke 3D volume (D, H, W); dimensi exact-nya unknown (None)\n    image.set_shape((None, None, None))\n    label.set_shape((None, None, None))\n\n    # Kembalikan PERSIS seperti input ke prepare_inputs: (image, label)\n    return image, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:12.420456Z","iopub.execute_input":"2025-12-07T16:37:12.421109Z","iopub.status.idle":"2025-12-07T16:37:12.425945Z","shell.execute_reply.started":"2025-12-07T16:37:12.421086Z","shell.execute_reply":"2025-12-07T16:37:12.425208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_inputs(image, label):\n    # Only take gt 1 \n    label = (label == 1)\n    \n    # Add channel dimension\n    image = image[..., None] # (D, H, W, 1)\n    label = label[..., None] # (D, H, W, 1)\n\n    # Convert to float32\n    image = tf.cast(image, tf.float32)\n    label = tf.cast(label, tf.float32)\n    \n    return image, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:14.08971Z","iopub.execute_input":"2025-12-07T16:37:14.090349Z","iopub.status.idle":"2025-12-07T16:37:14.094834Z","shell.execute_reply.started":"2025-12-07T16:37:14.090318Z","shell.execute_reply":"2025-12-07T16:37:14.09395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_tiff_dataset(dataset, batch_size=1, shuffle=True):\n    # Ambil list path\n    image_paths = [sample[\"image\"] for sample in dataset]\n    label_paths = [sample[\"label\"] for sample in dataset]\n\n    ds = tf.data.Dataset.from_tensor_slices((image_paths, label_paths))\n\n    if shuffle:\n        ds = ds.shuffle(buffer_size=len(image_paths))\n\n    # 1) Baca TIFF → (image, label) uint8, shape (D,H,W)\n    ds = ds.map(\n        parse_tiff_fn,\n        num_parallel_calls=tf.data.AUTOTUNE\n    )\n\n    # 2) Apply prepare_inputs (sama persis kaya pipeline TFRecord)\n    ds = ds.map(\n        prepare_inputs,\n        num_parallel_calls=tf.data.AUTOTUNE\n    )\n\n    # 3) Augment / transform sesuai mode\n    if shuffle:\n        ds = ds.map(\n            train_transformation,\n            num_parallel_calls=tf.data.AUTOTUNE\n        )\n    else:\n        ds = ds.map(\n            val_transformation,\n            num_parallel_calls=tf.data.AUTOTUNE\n        )\n\n    # 4) Batch + prefetch\n    ds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return ds\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:14.313796Z","iopub.execute_input":"2025-12-07T16:37:14.314084Z","iopub.status.idle":"2025-12-07T16:37:14.31996Z","shell.execute_reply.started":"2025-12-07T16:37:14.314062Z","shell.execute_reply":"2025-12-07T16:37:14.319205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\n\ndef split_dataset(dataset, val_ratio=0.2, seed=42):\n    random.seed(seed)\n    dataset = dataset.copy()\n    random.shuffle(dataset)\n\n    n_total = len(dataset)\n    n_val = int(n_total * val_ratio)\n\n    val_set = dataset[:n_val]\n    train_set = dataset[n_val:]\n\n    return train_set, val_set","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:14.511784Z","iopub.execute_input":"2025-12-07T16:37:14.51233Z","iopub.status.idle":"2025-12-07T16:37:14.516554Z","shell.execute_reply.started":"2025-12-07T16:37:14.512309Z","shell.execute_reply":"2025-12-07T16:37:14.515901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\nimport os\n\nimage_dir = \"/kaggle/input/vesuvius-challenge-surface-detection/train_images\"\nlabel_dir = \"/kaggle/input/vesuvius-challenge-surface-detection/train_labels\"\n\nimage_paths = sorted(glob.glob(os.path.join(image_dir, \"*.tif\")))\nlabel_paths = sorted(glob.glob(os.path.join(label_dir, \"*.tif\")))\n\ndataset = [\n    {\"image\": im_path, \"label\": lb_path}\n    for im_path, lb_path in zip(image_paths, label_paths)\n]\n\ntrain_set, val_set = split_dataset(dataset)\n\ntrain_ds = load_tiff_dataset(train_set, batch_size=6, shuffle=True)\nval_ds = load_tiff_dataset(train_set, batch_size=1, shuffle=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:14.776088Z","iopub.execute_input":"2025-12-07T16:37:14.776424Z","iopub.status.idle":"2025-12-07T16:37:17.266165Z","shell.execute_reply.started":"2025-12-07T16:37:14.77639Z","shell.execute_reply":"2025-12-07T16:37:17.265602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# x, y = next(iter(train_ds))\n# x.shape, y.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:37:17.267116Z","iopub.execute_input":"2025-12-07T16:37:17.267385Z","iopub.status.idle":"2025-12-07T16:37:23.154437Z","shell.execute_reply.started":"2025-12-07T16:37:17.267368Z","shell.execute_reply":"2025-12-07T16:37:23.153663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"run_training(\n    train_ds, val_ds, model, epochs=20\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:39:11.734672Z","iopub.execute_input":"2025-12-07T16:39:11.735207Z","iopub.status.idle":"2025-12-07T16:45:33.064512Z","shell.execute_reply.started":"2025-12-07T16:39:11.735151Z","shell.execute_reply":"2025-12-07T16:45:33.063621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tifffile as tiff\nimport numpy as np\n\ndef load_tiff_3d_np(path):\n    volume = tiff.imread(path)      # bisa return (D, H, W) atau (H, W, D) atau (H, W)\n    volume = volume.astype(\"float32\")\n\n    # Normalisasi 0–1, asumsi 16-bit TIFF (Vesuvius biasanya 0–65535)\n    max_val = volume.max() if volume.max() > 0 else 1.0\n    volume = volume / max_val\n\n    # Pastikan ada channel dimension\n    if volume.ndim == 3:\n        # asumsikan (D, H, W) → tambahkan channel=1 → (D, H, W, 1)\n        volume = volume[..., np.newaxis]\n    elif volume.ndim == 2:\n        # (H, W) → (1, H, W, 1) misal dianggap depth=1\n        volume = volume[np.newaxis, ..., np.newaxis]\n\n    return volume\n\npath = \"/kaggle/input/vesuvius-challenge-surface-detection/test_images/1407735.tif\"\nvol_np = load_tiff_3d_np(path)\nprint(\"numpy shape:\", vol_np.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:45:33.066559Z","iopub.execute_input":"2025-12-07T16:45:33.066883Z","iopub.status.idle":"2025-12-07T16:45:33.55122Z","shell.execute_reply.started":"2025-12-07T16:45:33.066853Z","shell.execute_reply":"2025-12-07T16:45:33.55049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"res = swi(np.expand_dims(vol_np, axis=0))\nsegres = (res > 0.35).astype(np.uint8)\ntiff.imwrite(\"/kaggle/working/1407735.tif\", segres[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:45:33.552018Z","iopub.execute_input":"2025-12-07T16:45:33.552282Z","iopub.status.idle":"2025-12-07T16:45:39.998494Z","shell.execute_reply.started":"2025-12-07T16:45:33.552264Z","shell.execute_reply":"2025-12-07T16:45:39.997723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import zipfile\nimport os\n\noutput_zip = \"submission.zip\"\npred_dir = \"/kaggle/working\"\n\nwith zipfile.ZipFile(output_zip, \"w\") as zipf:\n    for filename in os.listdir(pred_dir):\n        if filename.endswith(\".tif\"):\n            zipf.write(\n                os.path.join(pred_dir, filename),\n                arcname=filename  # penting: hanya nama file, tanpa path\n            )\n\nprint(\"ZIP created:\", output_zip)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-07T16:45:39.999687Z","iopub.execute_input":"2025-12-07T16:45:39.999925Z","iopub.status.idle":"2025-12-07T16:45:40.071544Z","shell.execute_reply.started":"2025-12-07T16:45:39.999907Z","shell.execute_reply":"2025-12-07T16:45:40.070998Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}