{"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":14878066,"sourceType":"competition"}],"dockerImageVersionId":31239,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-27T06:14:40.950832Z","iopub.execute_input":"2025-12-27T06:14:40.951174Z","iopub.status.idle":"2025-12-27T06:14:40.956312Z","shell.execute_reply.started":"2025-12-27T06:14:40.951147Z","shell.execute_reply":"2025-12-27T06:14:40.955375Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Recod.ai/LUC - Scientific Image Forgery Detection Contest.\n\n**This algorithm focuses on copy-move forgery detection (CMFD) using the U-Net architecture, an efficient architecture for biomedical image segmentation.**","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport json\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom tqdm import tqdm\n\n# --- 1. RLE ENCODING (OFFICIAL & FIXED) ---\n# --- 1. MÃ HÓA RLE (CHÍNH THỨC & ĐÃ SỬA LỖI) ---\ndef rle_encode(masks: list, fg_val: int = 1) -> str:\n    all_rles = []\n    for mask in masks:\n        # Thứ tự Fortran (cột trước) theo quy định cuộc thi\n        dots = np.where(mask.T.flatten() == fg_val)[0]\n        run_lengths = []\n        prev = -2\n        for b in dots:\n            if b > prev + 1:\n                # Ép kiểu int() để JSON có thể mã hóa (fix lỗi int64)\n                run_lengths.extend((int(b + 1), 0))\n            run_lengths[-1] += 1\n            prev = b\n        all_rles.append(json.dumps(run_lengths))\n    return \";\".join(all_rles)\n\n# --- 2. MODEL ARCHITECTURE ---\n# --- 2. KIẾN TRÚC MÔ HÌNH ---\nclass SimpleUNet(nn.Module):\n    def __init__(self):\n        super(SimpleUNet, self).__init__()\n        def conv_block(in_ch, out_ch):\n            return nn.Sequential(\n                nn.Conv2d(in_ch, out_ch, 3, padding=1),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(out_ch, out_ch, 3, padding=1),\n                nn.ReLU(inplace=True)\n            )\n        self.enc1 = conv_block(3, 32)\n        self.pool = nn.MaxPool2d(2)\n        self.up1 = nn.ConvTranspose2d(32, 32, kernel_size=2, stride=2)\n        self.final = nn.Conv2d(32, 1, kernel_size=1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        p1 = self.pool(e1)\n        d1 = self.up1(p1)\n        if d1.shape != e1.shape:\n            d1 = torch.nn.functional.interpolate(d1, size=e1.shape[2:])\n        return self.sigmoid(self.final(d1))\n\n# --- 3. RUN INFERENCE ---\n# --- 3. CHẠY DỰ ĐOÁN ---\ndef run_inference():\n    # Kiểm tra thiết bị (Device check)\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    test_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\n    sample_sub_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv'\n    \n    # 3.1 Đọc file mẫu và xử lý kiểu dữ liệu\n    sample_sub = pd.read_csv(sample_sub_path)\n    id_col = sample_sub.columns[0] \n    print(f\"Using ID column: {id_col}\")\n    \n    # QUAN TRỌNG: Ép kiểu cột ID về string để tránh lỗi merge\n    sample_sub[id_col] = sample_sub[id_col].astype(str)\n\n    model = SimpleUNet().to(device)\n    model.eval()\n\n    transform = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.Resize((256, 256)),\n        transforms.ToTensor(),\n    ])\n\n    test_images = [f for f in os.listdir(test_dir) if f.endswith(('.png', '.jpg'))]\n    results = []\n\n    print(f\"Processing {len(test_images)} images...\")\n    with torch.no_grad():\n        for img_name in tqdm(test_images):\n            img_path = os.path.join(test_dir, img_name)\n            image_src = cv2.imread(img_path)\n            if image_src is None: continue\n            \n            h, w, _ = image_src.shape\n            img_input = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)\n            img_input = transform(img_input).unsqueeze(0).to(device)\n\n            # Dự đoán (Prediction)\n            pred = model(img_input)\n            pred = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\n\n            mask_resized = cv2.resize(pred, (w, h), interpolation=cv2.INTER_NEAREST)\n            rle_str = rle_encode([mask_resized]) if np.sum(mask_resized) > 0 else \"\"\n\n            # Lưu ID dưới dạng string\n            results.append({\n                id_col: str(img_name.split('.')[0]),\n                \"annotation\": rle_str\n            })\n\n    # --- 4. EXPORT SUBMISSION (FIXED MERGE) ---\n    # --- 4. XUẤT FILE NỘP BÀI (ĐÃ SỬA LỖI MERGE) ---\n    submission_df = pd.DataFrame(results)\n    \n    # Merge an toàn sau khi đã đồng nhất kiểu string\n    final_df = sample_sub[[id_col]].merge(submission_df, on=id_col, how='left').fillna(\"\")\n    \n    final_df.to_csv(\"submission.csv\", index=False)\n    print(f\"Success! Saved submission.csv with {len(final_df)} rows.\")\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T06:14:40.957838Z","iopub.execute_input":"2025-12-27T06:14:40.958152Z","iopub.status.idle":"2025-12-27T06:14:41.102609Z","shell.execute_reply.started":"2025-12-27T06:14:40.958125Z","shell.execute_reply":"2025-12-27T06:14:41.101312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"------------------------------------------------------------\n**BACKUP RESULTS WHEN RUNNING THE CODE ABOVE:**\n\n**BACKUP KẾT QUẢ KHI CHẠY CODE TRÊN:**\n\nDevice: cpu\n\nUsing ID column: case_id\n\nProcessing 1 images...\n\n100%|██████████| 1/1 [00:00<00:00, 10.86it/s]\n\nSuccess! Saved submission.csv with 1 rows.\n\n------------------------------------------------------------","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom tqdm import tqdm\n\n# --- 1. DATASET CHUYÊN DỤNG CHO HUẤN LUYỆN ---\n# --- 1. TRAINING DATASET CLASS ---\nclass ForgeryTrainDataset(Dataset):\n    def __init__(self, img_dirs, mask_dir, transform=None):\n        \"\"\"\n        img_dirs: Danh sách các thư mục chứa ảnh (authentic + forged)\n        mask_dir: Thư mục chứa các tệp .npy\n        \"\"\"\n        self.img_paths = []\n        self.mask_dir = mask_dir\n        self.transform = transform\n        \n        for d in img_dirs:\n            for f in os.listdir(d):\n                if f.endswith(('.png', '.jpg')):\n                    self.img_paths.append(os.path.join(d, f))\n\n    def __len__(self):\n        return len(self.img_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.img_paths[idx]\n        img_name = os.path.basename(img_path)\n        \n        # Đọc ảnh (Read Image)\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # Tìm mask tương ứng (Find corresponding mask)\n        # Mask thường có tên trùng với ảnh nhưng đuôi .npy\n        mask_name = img_name.replace('.png', '.npy').replace('.jpg', '.npy')\n        mask_path = os.path.join(self.mask_dir, mask_name)\n        \n        if os.path.exists(mask_path):\n            mask = np.load(mask_path)\n            if len(mask.shape) == 3: # Nếu mask là (N, H, W)\n                mask = np.max(mask, axis=0)\n        else:\n            # Nếu là ảnh authentic (thật), mask sẽ là màu đen toàn bộ\n            mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)\n\n        # Resize để đưa vào mô hình (Resize for U-Net)\n        image = cv2.resize(image, (256, 256))\n        mask = cv2.resize(mask, (256, 256), interpolation=cv2.INTER_NEAREST)\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        mask = torch.tensor(mask, dtype=torch.float32).unsqueeze(0)\n        return image, mask\n\n# --- 2. HÀM HUẤN LUYỆN (TRAINING LOOP) ---\n# --- 2. TRAIN FUNCTION ---\ndef train_model():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Bắt đầu huấn luyện trên thiết bị: {device}\")\n\n    # Đường dẫn dữ liệu (Paths)\n    train_dirs = [\n        '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/authentic',\n        '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged'\n    ]\n    mask_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks'\n\n    transform = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n\n    # Khởi tạo Dataset & DataLoader\n    dataset = ForgeryTrainDataset(train_dirs, mask_dir, transform=transform)\n    # Lấy một phần nhỏ để demo nếu CPU quá chậm, hoặc chạy full nếu có GPU\n    dataloader = DataLoader(dataset, batch_size=8, shuffle=True, num_workers=2)\n\n    # Khởi tạo Model (Sử dụng SimpleUNet từ ô trước)\n    model = SimpleUNet().to(device)\n    criterion = nn.BCELoss() # Binary Cross Entropy cho phân đoạn 2 lớp\n    optimizer = optim.Adam(model.parameters(), lr=1e-4)\n\n    # Huấn luyện (Epochs)\n    num_epochs = 3 # Tăng lên 10-20 nếu bạn có GPU và thời gian\n    model.train()\n\n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        pbar = tqdm(dataloader, desc=f\"Epoch {epoch+1}/{num_epochs}\")\n        \n        for images, masks in pbar:\n            images, masks = images.to(device), masks.to(device)\n            \n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, masks)\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            pbar.set_postfix({'loss': running_loss / len(dataloader)})\n\n    # Lưu mô hình (Save Model Weight)\n    torch.save(model.state_dict(), \"unet_forgery_model.pth\")\n    print(\"Huấn luyện hoàn tất! Đã lưu file: unet_forgery_model.pth\")\n\nif __name__ == \"__main__\":\n    train_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T06:18:42.576811Z","iopub.execute_input":"2025-12-27T06:18:42.577201Z","iopub.status.idle":"2025-12-27T07:09:50.806169Z","shell.execute_reply.started":"2025-12-27T06:18:42.577173Z","shell.execute_reply":"2025-12-27T07:09:50.803634Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"------------------------------------------------------------\n**BACKUP RESULTS WHEN RUNNING THE CODE ABOVE:**\n\n**BACKUP KẾT QUẢ KHI CHẠY CODE TRÊN:**\n\nBắt đầu huấn luyện trên thiết bị: cpu\n\nEpoch 1/3: 100%|██████████| 641/641 [16:22<00:00,  1.53s/it, loss=0.269] \n\nEpoch 2/3: 100%|██████████| 641/641 [16:59<00:00,  1.59s/it, loss=0.207] \n\nEpoch 3/3: 100%|██████████| 641/641 [17:45<00:00,  1.66s/it, loss=0.203] \n\nHuấn luyện hoàn tất! Đã lưu file: unet_forgery_model.pth\n\n------------------------------------------------------------\n","metadata":{}}]}