{"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":113558,"databundleVersionId":14174843,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# List all files in the dataset\n!ls /kaggle/input\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T16:56:32.877901Z","iopub.execute_input":"2025-10-24T16:56:32.878212Z","iopub.status.idle":"2025-10-24T16:56:33.012759Z","shell.execute_reply.started":"2025-10-24T16:56:32.87819Z","shell.execute_reply":"2025-10-24T16:56:33.011438Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Root path of the dataset\nDATASET_ROOT = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/\"\n\n# List all folders/files in the dataset root\nprint(os.listdir(DATASET_ROOT))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:01:01.016385Z","iopub.execute_input":"2025-10-24T17:01:01.016781Z","iopub.status.idle":"2025-10-24T17:01:01.024493Z","shell.execute_reply.started":"2025-10-24T17:01:01.016748Z","shell.execute_reply":"2025-10-24T17:01:01.022895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_image_with_mask(image_file, mask_file):\n    # Load the image\n    img = cv2.imread(image_file)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    # Load the mask (.npy) and remove any singleton dimensions\n    mask = np.load(mask_file)\n    mask = np.squeeze(mask)  # <-- this fixes the shape\n    \n    plt.figure(figsize=(12,6))\n    \n    plt.subplot(1,2,1)\n    plt.imshow(img)\n    plt.title(\"Original Image\")\n    plt.axis('off')\n    \n    plt.subplot(1,2,2)\n    plt.imshow(img)\n    plt.imshow(mask, cmap='jet', alpha=0.5)  # overlay mask\n    plt.title(\"Image with Mask Overlay\")\n    plt.axis('off')\n    \n    plt.show()\n\n# Show 3 random images with masks\nfor _ in range(3):\n    img_file = random.choice(image_files)\n    mask_file = random.choice(mask_files)  # random mask\n    show_image_with_mask(img_file, mask_file)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:49:35.484606Z","iopub.execute_input":"2025-10-24T17:49:35.485059Z","iopub.status.idle":"2025-10-24T17:49:38.18275Z","shell.execute_reply.started":"2025-10-24T17:49:35.485023Z","shell.execute_reply":"2025-10-24T17:49:38.181202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================\n# 1️⃣ Imports\n# ==========================\nimport os\nimport cv2\nimport numpy as np\nimport random\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.callbacks import ModelCheckpoint\nimport matplotlib.pyplot as plt\n\n# ==========================\n# 2️⃣ Paths\n# ==========================\nBASE_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nTRAIN_PATH = os.path.join(BASE_PATH, \"train_images\")\nMASK_PATH = os.path.join(BASE_PATH, \"train_masks\")\n\n# ==========================\n# 3️⃣ Load Image-Mask Pairs\n# ==========================\nimage_files = []\nmask_files = []\n\nfor label in [\"authentic\", \"forged\"]:\n    folder = os.path.join(TRAIN_PATH, label)\n    if os.path.exists(folder):\n        for img_name in os.listdir(folder):\n            img_path = os.path.join(label, img_name)\n            mask_name = img_name.split(\".\")[0] + \".npy\"\n            mask_path = os.path.join(MASK_PATH, mask_name)\n\n            if os.path.exists(mask_path):\n                image_files.append(img_path)\n                mask_files.append(mask_name)\n\nprint(f\"✅ Found {len(image_files)} potential image-mask pairs\")\n\n# ==========================\n# 4️⃣ Data Generator (Fixed)\n# ==========================\nclass DataGenerator(keras.utils.Sequence):\n    def __init__(self, image_files, mask_files, batch_size=8, img_size=(256, 256), shuffle=True):\n        self.image_files = image_files\n        self.mask_files = mask_files\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.shuffle = shuffle\n        self.on_epoch_end()\n\n    def __len__(self):\n        return len(self.image_files) // self.batch_size\n\n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.image_files))\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n\n    def __getitem__(self, index):\n        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]\n        batch_images, batch_masks = [], []\n\n        for i in indexes:\n            img_path = os.path.join(TRAIN_PATH, self.image_files[i])\n            mask_path = os.path.join(MASK_PATH, self.mask_files[i])\n\n            img = cv2.imread(img_path)\n            if img is None:\n                continue\n\n            # Load mask safely\n            try:\n                mask = np.load(mask_path)\n            except:\n                continue\n\n            # Skip empty or invalid masks\n            if mask is None or mask.size == 0:\n                continue\n\n            if len(mask.shape) > 2:\n                mask = mask[..., 0]\n\n            # Ensure both resize work\n            img = cv2.resize(img, self.img_size)\n            mask = cv2.resize(mask, self.img_size, interpolation=cv2.INTER_NEAREST)\n\n            img = img.astype(\"float32\") / 255.0\n            mask = np.expand_dims(mask, axis=-1)\n            mask = (mask > 0.5).astype(\"float32\")\n\n            batch_images.append(img)\n            batch_masks.append(mask)\n\n        if len(batch_images) == 0:\n            # In case entire batch skipped, load next random valid one\n            return self.__getitem__((index + 1) % len(self))\n\n        return np.array(batch_images), np.array(batch_masks)\n\n# ==========================\n# 5️⃣ UNet Model\n# ==========================\ndef build_unet(input_shape=(256, 256, 3)):\n    inputs = keras.Input(shape=input_shape)\n\n    def conv_block(x, filters):\n        x = layers.Conv2D(filters, 3, padding=\"same\", activation=\"relu\")(x)\n        x = layers.Conv2D(filters, 3, padding=\"same\", activation=\"relu\")(x)\n        return x\n\n    def encoder_block(x, filters):\n        f = conv_block(x, filters)\n        p = layers.MaxPooling2D((2, 2))(f)\n        return f, p\n\n    def decoder_block(x, skip, filters):\n        x = layers.Conv2DTranspose(filters, (2, 2), strides=2, padding=\"same\")(x)\n        x = layers.Concatenate()([x, skip])\n        x = conv_block(x, filters)\n        return x\n\n    f1, p1 = encoder_block(inputs, 64)\n    f2, p2 = encoder_block(p1, 128)\n    f3, p3 = encoder_block(p2, 256)\n    f4, p4 = encoder_block(p3, 512)\n\n    bottleneck = conv_block(p4, 1024)\n\n    d1 = decoder_block(bottleneck, f4, 512)\n    d2 = decoder_block(d1, f3, 256)\n    d3 = decoder_block(d2, f2, 128)\n    d4 = decoder_block(d3, f1, 64)\n\n    outputs = layers.Conv2D(1, (1, 1), activation=\"sigmoid\")(d4)\n\n    model = keras.Model(inputs, outputs)\n    return model\n\n# ==========================\n# 6️⃣ Train Model\n# ==========================\nmodel = build_unet()\nmodel.compile(optimizer=\"adam\", loss=\"binary_crossentropy\", metrics=[\"accuracy\"])\n\ntrain_gen = DataGenerator(image_files, mask_files, batch_size=4, img_size=(256, 256))\n\ncheckpoint = ModelCheckpoint(\"unet_best.h5\", save_best_only=True, monitor=\"loss\", mode=\"min\")\nhistory = model.fit(train_gen, epochs=5, callbacks=[checkpoint])\n\n# ==========================\n# 7️⃣ Plot Results\n# ==========================\nplt.plot(history.history[\"loss\"], label=\"loss\")\nplt.plot(history.history[\"accuracy\"], label=\"accuracy\")\nplt.legend()\nplt.title(\"Training Progress\")\nplt.show()\n\nprint(\"✅ Training complete! Model saved as unet_best.h5\")\nv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-26T17:47:58.088756Z","iopub.execute_input":"2025-10-26T17:47:58.089087Z"}},"outputs":[],"execution_count":null}]}