{"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":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14878066,"sourceType":"competition"},{"sourceId":288658227,"sourceType":"kernelVersion"}],"dockerImageVersionId":31234,"isInternetEnabled":false,"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-27T08:47:55.769644Z","iopub.execute_input":"2025-12-27T08:47:55.770088Z","iopub.status.idle":"2025-12-27T08:47:55.776010Z","shell.execute_reply.started":"2025-12-27T08:47:55.770054Z","shell.execute_reply":"2025-12-27T08:47:55.774593Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**The training process involved creating a model to run the competition entry code offline!**\n\n**Quá trình huấn luyện tạo mô hình để chạy không internet của code bài dự thi!**\n\nhttps://www.kaggle.com/code/trnhquangminh140/model-scientific-image-forgery-detection-minh","metadata":{}},{"cell_type":"markdown","source":"# The Copy-Move Forgery Detection (CMFD) problem","metadata":{}},{"cell_type":"markdown","source":"# Cấu trúc giải thuật (Algorithm Structure)\n\n**Dataloader: Đọc hình ảnh và các tệp .npy (mask).**\n\n**Model: Sử dụng kiến trúc U-Net đơn giản (để đảm bảo chạy trong giới hạn 4h CPU/GPU mà không cần tải thêm trọng số từ internet).**\n\n**Post-processing: Tìm các vùng trùng lặp.**\n\n**Submission: Mã hóa RLE bằng hàm recodai_f1 chính thức.**","metadata":{}},{"cell_type":"markdown","source":"**Internet Disabled**: The code above does not use pip install or load payloads from torchvision.models. I have defined a SimpleUNet directly in the code. If you have a pre-trained model (.pth), upload it to Kaggle as a Dataset and load it using torch.load('/kaggle/input/your-model-path/model.pth').\n**Internet Disabled**: Code trên không sử dụng pip install hay tải trọng số từ torchvision.models. Tôi đã định nghĩa một SimpleUNet ngay trong code. Nếu bạn có mô hình đã huấn luyện trước (.pth), hãy upload nó lên Kaggle dưới dạng Dataset và load bằng torch.load('/kaggle/input/your-model-path/model.pth').\n\n**RLE Encoding**: I have integrated the _rle_encode_jit function using the numba library. This library is available on Kaggle and helps compress mask data extremely quickly, avoiding timeouts.\n**RLE Encoding**: Tôi đã tích hợp hàm _rle_encode_jit sử dụng thư viện numba. Thư viện này có sẵn trên Kaggle và giúp việc nén dữ liệu mask cực nhanh, tránh bị quá thời gian (Timeout).\n\n**Memory Management**: With a 4-hour limit, using torch.no_grad() and deleting unnecessary variables is mandatory to avoid Out of Memory (OOM) errors.\n**Memory Management**: Với giới hạn 4 giờ, việc sử dụng torch.no_grad() và xóa các biến không cần thiết là bắt buộc để tránh lỗi Out of Memory (OOM).\n\n**Submission Format**: The submission.csv file will have two columns: row_id (image file name without extension) and annotation (RLE string).\n**Submission Format**: File submission.csv sẽ có hai cột: row_id (tên file ảnh không có đuôi) và annotation (chuỗi RLE).\n","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport json\nimport numba\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport numpy.typing as npt\n\n# --- 1. RLE ENCODING FUNCTIONS (FROM OFFICIAL METRIC) ---\n# --- 1. CÁC HÀM MÃ HÓA RLE (TỪ THƯỚC ĐO CHÍNH THỨC) ---\n\n@numba.jit(nopython=True)\ndef _rle_encode_jit(x: npt.NDArray, fg_val: int = 1) -> list:\n    \"\"\"Numba-jitted RLE encoder for speed / Bộ mã hóa RLE dùng Numba để tăng tốc\"\"\"\n    dots = np.where(x.T.flatten() == fg_val)[0]\n    run_lengths = []\n    prev = -2\n    for b in dots:\n        if b > prev + 1:\n            run_lengths.extend((b + 1, 0))\n        run_lengths[-1] += 1\n        prev = b\n    return run_lengths\n\ndef rle_encode(masks: list, fg_val: int = 1) -> str:\n    \"\"\"Encodes list of masks to semicolon-separated RLE string / Mã hóa danh sách mask thành chuỗi RLE\"\"\"\n    encoded_masks = [json.dumps(_rle_encode_jit(m, fg_val)) for m in masks]\n    return \";\".join(encoded_masks)\n\n# --- 2. MODEL DEFINITION (U-NET) ---\n# --- 2. ĐỊNH NGHĨA MÔ HÌNH (U-NET) ---\n\nclass SimpleUNet(nn.Module):\n    \"\"\"A basic U-Net for offline segmentation / Mô hình U-Net cơ bản cho phân đoạn ngoại tuyến\"\"\"\n    def __init__(self):\n        super(SimpleUNet, self).__init__()\n        def conv_block(in_c, out_c):\n            return nn.Sequential(\n                nn.Conv2d(in_c, out_c, kernel_size=3, padding=1),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(out_c, out_c, kernel_size=3, padding=1),\n                nn.ReLU(inplace=True)\n            )\n        \n        self.enc1 = conv_block(3, 64)\n        self.pool = nn.MaxPool2d(2)\n        self.enc2 = conv_block(64, 128)\n        self.dec = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n        self.final = nn.Conv2d(128, 1, kernel_size=1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.pool(e1))\n        d = self.dec(e2)\n        # Cat e1 and d for skip connection / Kết nối tắt e1 và d\n        out = self.final(torch.cat([e1, d], dim=1))\n        return self.sigmoid(out)\n\n# --- 3. DATASET HANDLING ---\n# --- 3. XỬ LÝ DỮ LIỆU ---\n\nclass ForgeryDataset(Dataset):\n    def __init__(self, img_dir, img_list, transform=None):\n        self.img_dir = img_dir\n        self.img_list = img_list\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.img_list)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.img_dir, self.img_list[idx])\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        orig_shape = image.shape[:2]\n        \n        # Resize for model input / Thay đổi kích thước cho mô hình\n        image = cv2.resize(image, (256, 256))\n        image = image.transpose(2, 0, 1) / 255.0\n        return torch.tensor(image, dtype=torch.float32), self.img_list[idx], orig_shape\n\n# --- 4. INFERENCE LOOP ---\n# --- 4. VÒNG LẶP DỰ ĐOÁN ---\n\ndef run_inference():\n    # Setup paths / Thiết lập đường dẫn\n    test_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\n    test_imgs = [f for f in os.listdir(test_dir) if f.endswith('.png')]\n    \n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    model = SimpleUNet().to(device)\n    model.eval() # Evaluation mode / Chế độ đánh giá\n    \n    results = []\n    \n    print(f\"Processing {len(test_imgs)} images... / Đang xử lý {len(test_imgs)} hình ảnh...\")\n\n    with torch.no_grad():\n        for img_name in test_imgs:\n            # Load and preprocess / Tải và tiền xử lý\n            img_path = os.path.join(test_dir, img_name)\n            img_orig = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n            h, w = img_orig.shape\n            \n            # Dummy logic: for demonstration, we detect the most salient duplicated-looking spots\n            # logic giả định: dự đoán vùng bị làm giả (thay thế bằng model.forward nếu đã train)\n            # Ở đây ta tạo một mask ngẫu nhiên hoặc từ mô hình\n            \n            # Giả sử mô hình trả về mask (h, w)\n            # mask = model(input_tensor)...\n            \n            # Ví dụ: Tạo mask trống nếu không tìm thấy giả mạo\n            mask = np.zeros((h, w), dtype=np.uint8)\n            \n            # Encode and store / Mã hóa và lưu trữ\n            # Note: The metric expects a list of masks if there are multiple duplicated regions\n            # Lưu ý: Thước đo yêu cầu một danh sách các mask nếu có nhiều vùng trùng lặp\n            rle_string = rle_encode([mask])\n            \n            results.append({\n                \"row_id\": img_name.replace('.png', ''),\n                \"annotation\": rle_string\n            })\n\n    # --- 5. CREATE SUBMISSION FILE ---\n    # --- 5. TẠO TỆP NỘP BÀI ---\n    submission_df = pd.DataFrame(results)\n    submission_df.to_csv(\"submission.csv\", index=False)\n    print(\"Submission saved successfully! / Lưu tệp nộp bài thành công!\")\n\nif __name__ == \"__main__\":\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:47:55.778215Z","iopub.execute_input":"2025-12-27T08:47:55.778633Z","iopub.status.idle":"2025-12-27T08:47:56.220305Z","shell.execute_reply.started":"2025-12-27T08:47:55.778586Z","shell.execute_reply":"2025-12-27T08:47:56.218819Z"}},"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\nProcessing 1 images... / Đang xử lý 1 hình ảnh...\n\nSubmission saved successfully! / Lưu tệp nộp bài thành công!\n\n------------------------------------------------------------","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\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\n\n# --- 1. RLE ENCODING (OFFICIAL METRIC) ---\n# --- 1. MÃ HÓA RLE (THƯỚC ĐO CHÍNH THỨC) ---\ndef rle_encode(masks: list, fg_val: int = 1) -> str:\n    \"\"\"\n    Encodes masks to RLE string using Fortran order (column-major).\n    Mã hóa mask thành chuỗi RLE theo thứ tự Fortran (ưu tiên cột).\n    \"\"\"\n    all_rles = []\n    for mask in masks:\n        # Transpose for column-major order / Chuyển vị để đúng thứ tự cột\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                # Cast to Python int for JSON / Ép kiểu int để tránh lỗi JSON\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. U-NET MODEL ARCHITECTURE ---\n# --- 2. KIẾN TRÚC MÔ HÌNH U-NET ---\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        # Encoder / Bộ mã hóa\n#       self.enc1 = conv_block(3, 64)\n        self.enc1 = conv_block(3, 32)\n        self.pool = nn.MaxPool2d(2)\n        # Decoder / Bộ giải mã\n#       self.up1 = nn.ConvTranspose2d(64, 64, kernel_size=2, stride=2)\n#       self.final = nn.Conv2d(64, 1, kernel_size=1)\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        # Match size if needed / Khớp kích thước nếu cần\n        if d1.shape != e1.shape:\n            d1 = torch.nn.functional.interpolate(d1, size=e1.shape[2:])\n        return self.sigmoid(self.final(d1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:47:56.222225Z","iopub.execute_input":"2025-12-27T08:47:56.222615Z","iopub.status.idle":"2025-12-27T08:47:56.236349Z","shell.execute_reply.started":"2025-12-27T08:47:56.222568Z","shell.execute_reply":"2025-12-27T08:47:56.234889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. RUN INFERENCE (OFFLINE MODE) ---\n# --- 3. CHẠY DỰ ĐOÁN (CHẾ ĐỘ NGOẠI TUYẾN) ---\ndef run_inference():\n    # Use GPU if available / Sử dụng GPU nếu có\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    # Data paths / Đường dẫn dữ liệu\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    # Path to your uploaded model / Đường dẫn tới mô hình bạn đã tải lên\n    model_path = '/kaggle/input/unet_forgery_model.pth' \n\n    # Load Model / Nạp mô hình\n    model = SimpleUNet().to(device)\n    if os.path.exists(model_path):\n        model.load_state_dict(torch.load(model_path, map_location=device))\n        print(\"Pre-trained model loaded! / Đã nạp mô hình!\")\n    else:\n        # Check in current working directory if not in input\n        backup_path = '/kaggle/working/unet_forgery_model.pth'\n        if os.path.exists(backup_path):\n            model.load_state_dict(torch.load(backup_path, map_location=device))\n            print(\"Loaded model from working directory.\")\n        else:\n            print(\"Warning: Model weight file not found!\")\n\n    model.eval()\n\n    # Prep metadata / Chuẩn bị thông tin tệp mẫu\n    sample_sub = pd.read_csv(sample_sub_path)\n    id_col = sample_sub.columns[0] # Usually 'case_id'\n    sample_sub[id_col] = sample_sub[id_col].astype(str)\n\n    # Transforms / Biến đổi ảnh\n    transform = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.Resize((256, 256)),\n        transforms.ToTensor(),\n    ])\n\n    test_files = [f for f in os.listdir(test_dir) if f.endswith(('.png', '.jpg'))]\n    results = []\n\n    print(f\"Processing {len(test_files)} images...\")\n    with torch.no_grad():\n        for img_name in tqdm(test_files):\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            # Inference / Dự đoán\n            pred = model(img_input)\n            pred = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\n\n            # Post-process / Hậu xử lý\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            results.append({\n                id_col: str(img_name.split('.')[0]),\n                \"annotation\": rle_str\n            })\n\n    # Create submission / Tạo file nộp bài\n    submission_df = pd.DataFrame(results)\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(\"Success! 'submission.csv' created. / Thành công! Đã tạo submission.csv.\")\n\n# --- 4. DEMO EXAMPLE ---\n# --- 4. VÍ DỤ DEMO ---\ndef run_demo():\n    demo_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged/10015.png'\n    if os.path.exists(demo_path):\n        print(f\"Demo image loaded: {demo_path}\")\n    else:\n        print(\"Demo path not found.\")\n\nif __name__ == \"__main__\":\n    run_demo()\n    run_inference()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:47:56.239400Z","iopub.execute_input":"2025-12-27T08:47:56.240333Z","iopub.status.idle":"2025-12-27T08:47:56.399876Z","shell.execute_reply.started":"2025-12-27T08:47:56.240126Z","shell.execute_reply":"2025-12-27T08:47:56.398862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. INFERENCE & SUBMISSION BLOCK ---\n# --- 6. KHỐI DỰ ĐOÁN & TẠO FILE NỘP BÀI ---\n\nimport os\nimport cv2\nimport json\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom torchvision import transforms\n\n# Định nghĩa đường dẫn dữ liệu\n# /kaggle/input/model-scientific-image-forgery-detection-minh/unet_forgery_model.pth\ntest_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\nsample_sub_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv'\nmodel_path = '/kaggle/input/model-scientific-image-forgery-detection-minh/unet_forgery_model.pth'\n\n# Tải mô hình đã huấn luyện trước\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = SimpleUNet().to(device)\nmodel.load_state_dict(torch.load(model_path, map_location=device))\nmodel.eval()\nprint(\"Mô hình đã nạp thành công!\")\n\n# Biến đổi ảnh đầu vào\ntransform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((256, 256)),\n    transforms.ToTensor()\n])\n\n# Hàm mã hóa RLE chính thức\ndef rle_encode(masks: list, fg_val: int = 1) -> str:\n    all_rles = []\n    for mask in masks:\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                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# Dự đoán và tạo submission.csv\nresults = []\ntest_files = [f for f in os.listdir(test_dir) if f.endswith('.png')]\nprint(f\"Đang xử lý {len(test_files)} hình ảnh...\")\n\nwith torch.no_grad():\n    for img_name in test_files:\n        img_path = os.path.join(test_dir, img_name)\n        image_src = cv2.imread(img_path)\n        if image_src is None:\n            continue\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        pred = model(img_input)\n        pred = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\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 \"authentic\"\n\n        results.append({\n            \"case_id\": img_name.replace('.png', ''),\n            \"annotation\": rle_str\n        })\n\n# Tạo file submission.csv\nsubmission_df = pd.DataFrame(results)\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ Đã tạo file submission.csv thành công!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:47:56.401740Z","iopub.execute_input":"2025-12-27T08:47:56.402034Z","iopub.status.idle":"2025-12-27T08:47:56.517359Z","shell.execute_reply.started":"2025-12-27T08:47:56.402005Z","shell.execute_reply":"2025-12-27T08:47:56.516488Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 7. DEMO VỚI ẢNH GIẢ MẠO ---\n# --- 7. DEMO WITH FORGED IMAGE ---\n\nimport matplotlib.pyplot as plt\n\ndef run_demo():\n    demo_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged/10015.png'\n    if not os.path.exists(demo_path):\n        print(\"❌ Không tìm thấy ảnh demo.\")\n        return\n\n    print(f\"✅ Đang chạy demo với ảnh giả mạo: {demo_path}\")\n    image_src = cv2.imread(demo_path)\n    h, w, _ = image_src.shape\n    img_input = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)\n    img_tensor = transform(img_input).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        pred = model(img_tensor)\n        pred_mask = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\n        mask_resized = cv2.resize(pred_mask, (w, h), interpolation=cv2.INTER_NEAREST)\n\n    # Hiển thị ảnh gốc và mặt nạ dự đoán\n    fig, axs = plt.subplots(1, 2, figsize=(12, 6))\n    axs[0].imshow(img_input)\n    axs[0].set_title(\"Ảnh giả mạo gốc / Forged Image\")\n    axs[0].axis(\"off\")\n\n    axs[1].imshow(mask_resized, cmap='gray')\n    axs[1].set_title(\"Mặt nạ dự đoán / Predicted Mask\")\n    axs[1].axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n# Gọi hàm demo sau khi inference\nrun_demo()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:47:56.518650Z","iopub.execute_input":"2025-12-27T08:47:56.519296Z","iopub.status.idle":"2025-12-27T08:47:57.403064Z","shell.execute_reply.started":"2025-12-27T08:47:56.519252Z","shell.execute_reply":"2025-12-27T08:47:57.401659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. INFERENCE & SUBMISSION BLOCK ---\n# --- 6. KHỐI DỰ ĐOÁN & TẠO FILE NỘP BÀI ---\n\nimport os\nimport cv2\nimport json\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom torchvision import transforms\n\n# Định nghĩa đường dẫn dữ liệu\ntest_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\nsample_sub_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv'\n# model_path = '/kaggle/input/unet_forgery_model/unet_forgery_model.pth'\nmodel_path = '/kaggle/input/model-scientific-image-forgery-detection-minh/unet_forgery_model.pth'\n\n# Tải mô hình đã huấn luyện trước\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = SimpleUNet().to(device)\nmodel.load_state_dict(torch.load(model_path, map_location=device))\nmodel.eval()\n\n# Biến đổi ảnh đầu vào\ntransform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((256, 256)),\n    transforms.ToTensor()\n])\n\n# Hàm mã hóa RLE chính thức\ndef rle_encode(masks: list, fg_val: int = 1) -> str:\n    all_rles = []\n    for mask in masks:\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                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# Dự đoán và tạo submission.csv\nresults = []\ntest_files = [f for f in os.listdir(test_dir) if f.endswith('.png')]\nprint(f\"Đang xử lý {len(test_files)} hình ảnh...\")\n\nwith torch.no_grad():\n    for img_name in test_files:\n        img_path = os.path.join(test_dir, img_name)\n        image_src = cv2.imread(img_path)\n        if image_src is None:\n            continue\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        pred = model(img_input)\n        pred = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\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 \"authentic\"\n\n        results.append({\n            \"case_id\": img_name.replace('.png', ''),\n            \"annotation\": rle_str\n        })\n\n# Tạo file submission.csv\nsubmission_df = pd.DataFrame(results)\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ Đã tạo file submission.csv thành công!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:49:12.527303Z","iopub.execute_input":"2025-12-27T08:49:12.528093Z","iopub.status.idle":"2025-12-27T08:49:12.665377Z","shell.execute_reply.started":"2025-12-27T08:49:12.528051Z","shell.execute_reply":"2025-12-27T08:49:12.663895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 7. DEMO VỚI ẢNH GIẢ MẠO ---\n# --- 7. DEMO WITH FORGED IMAGE ---\n\nimport matplotlib.pyplot as plt\n\ndef run_demo():\n    demo_path = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged/10015.png'\n    if not os.path.exists(demo_path):\n        print(\"❌ Không tìm thấy ảnh demo.\")\n        return\n\n    print(f\"✅ Đang chạy demo với ảnh giả mạo: {demo_path}\")\n    image_src = cv2.imread(demo_path)\n    h, w, _ = image_src.shape\n    img_input = cv2.cvtColor(image_src, cv2.COLOR_BGR2RGB)\n    img_tensor = transform(img_input).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        pred = model(img_tensor)\n        pred_mask = (pred > 0.5).cpu().numpy().astype(np.uint8)[0][0]\n        mask_resized = cv2.resize(pred_mask, (w, h), interpolation=cv2.INTER_NEAREST)\n\n    # Hiển thị ảnh gốc và mặt nạ dự đoán\n    fig, axs = plt.subplots(1, 2, figsize=(12, 6))\n    axs[0].imshow(img_input)\n    axs[0].set_title(\"Ảnh giả mạo gốc / Forged Image\")\n    axs[0].axis(\"off\")\n\n    axs[1].imshow(mask_resized, cmap='gray')\n    axs[1].set_title(\"Mặt nạ dự đoán / Predicted Mask\")\n    axs[1].axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n\n# Gọi hàm demo sau khi inference\nrun_demo()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-27T08:49:21.335703Z","iopub.execute_input":"2025-12-27T08:49:21.336083Z","iopub.status.idle":"2025-12-27T08:49:22.218325Z","shell.execute_reply.started":"2025-12-27T08:49:21.336051Z","shell.execute_reply":"2025-12-27T08:49:22.217329Z"}},"outputs":[],"execution_count":null}]}