{"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"},{"sourceId":272822051,"sourceType":"kernelVersion"}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install --no-index --find-links /kaggle/input/yolo11-seg-config-ultralytics/ultralytics ultralytics \"numpy<2\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:50:00.682775Z","iopub.execute_input":"2025-11-02T13:50:00.683088Z","iopub.status.idle":"2025-11-02T13:53:47.950729Z","shell.execute_reply.started":"2025-11-02T13:50:00.683064Z","shell.execute_reply":"2025-11-02T13:53:47.949347Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport json\nimport torch\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom ultralytics import YOLO","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:53:47.952924Z","iopub.execute_input":"2025-11-02T13:53:47.953266Z","iopub.status.idle":"2025-11-02T13:54:55.050263Z","shell.execute_reply.started":"2025-11-02T13:53:47.953237Z","shell.execute_reply":"2025-11-02T13:54:55.049308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_IMAGES_DIR = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\nSAMPLE_SUBMISSION_PATH = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv'\nMODEL_PATH = '/kaggle/input/yolo11-seg-config-ultralytics/runs/segment/train/weights/best.pt'\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nmodel = YOLO(MODEL_PATH).to(device)\nmodel.eval()\n\ndef rle_encode(mask: np.ndarray, fg_val: int = 1) -> str:\n    pixels = mask.T.flatten()\n    dots = np.where(pixels == fg_val)[0]\n    if len(dots) == 0:\n        return \"authentic\"\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 json.dumps([int(x) for x in run_lengths])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:54:55.051314Z","iopub.execute_input":"2025-11-02T13:54:55.051769Z","iopub.status.idle":"2025-11-02T13:54:56.755485Z","shell.execute_reply.started":"2025-11-02T13:54:55.051740Z","shell.execute_reply":"2025-11-02T13:54:56.754321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Inference on test images\npredictions = {}\ntest_files = sorted(os.listdir(TEST_IMAGES_DIR))\n\nfor file in tqdm(test_files, desc=\"Inference\"):\n    case_id = os.path.splitext(file)[0]\n    img_path = os.path.join(TEST_IMAGES_DIR, file)\n    \n    # Uploading an image\n    image = Image.open(img_path).convert('RGB')\n    original_size = np.array(image).shape[:2]\n    \n    with torch.no_grad():\n        results = model(image, imgsz=512, verbose=False)\n\n    result = results[0]\n    masks = result.masks\n    boxes = result.boxes\n\n    if masks is None or len(masks) == 0:\n        predictions[case_id] = \"authentic\"\n        continue\n\n    # Confidence filtering\n    confs = boxes.conf.cpu().numpy()\n    mask_array = masks.data.cpu().numpy()\n\n    # Confidence threshold\n    CONF_THRESH = 0.56\n    valid = confs > CONF_THRESH\n\n    if not np.any(valid):\n        predictions[case_id] = \"authentic\"\n        continue\n\n    # Combining all valid masks\n    combined_mask_resized = np.zeros((original_size[0], original_size[1]), dtype=np.uint8)\n\n    for i in np.where(valid)[0]:\n        mask = mask_array[i]\n        # Resize the mask to the original size\n        resized_mask = cv2.resize(mask, (original_size[1], original_size[0]), interpolation=cv2.INTER_NEAREST)\n        combined_mask_resized = np.logical_or(combined_mask_resized, resized_mask > 0.5)\n\n    combined_mask_resized = combined_mask_resized.astype(np.uint8)\n\n    if combined_mask_resized.sum() == 0:\n        predictions[case_id] = \"authentic\"\n    else:\n        rle = rle_encode(combined_mask_resized)\n        predictions[case_id] = rle","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:54:56.757788Z","iopub.execute_input":"2025-11-02T13:54:56.758076Z","iopub.status.idle":"2025-11-02T13:54:59.044083Z","shell.execute_reply.started":"2025-11-02T13:54:56.758053Z","shell.execute_reply":"2025-11-02T13:54:59.042883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create submission\nsample_sub = pd.read_csv(SAMPLE_SUBMISSION_PATH)\nsubmission_rows = []\n\nfor _, row in sample_sub.iterrows():\n    case_id = str(row['case_id'])\n    annotation = predictions.get(case_id, \"authentic\")\n    submission_rows.append({'case_id': row['case_id'], 'annotation': annotation})\n\nsubmission = pd.DataFrame(submission_rows)\nsubmission.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:54:59.045059Z","iopub.execute_input":"2025-11-02T13:54:59.045767Z","iopub.status.idle":"2025-11-02T13:54:59.088687Z","shell.execute_reply.started":"2025-11-02T13:54:59.045735Z","shell.execute_reply":"2025-11-02T13:54:59.087425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"FORGED_DIR = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged'\nMASKS_DIR = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks'\nMODEL_PATH = '/kaggle/input/yolo11-seg-config-ultralytics/runs/segment/train/weights/best.pt'\n\n# Getting a list of forged images with masks\nforged_files = [f for f in os.listdir(FORGED_DIR) if f.endswith(('.png', '.jpg', '.jpeg'))]\n\n# We will leave only those that have an .npy mask\nvalid_files = []\nfor f in forged_files:\n    base = os.path.splitext(f)[0]\n    if os.path.exists(os.path.join(MASKS_DIR, f\"{base}.npy\")):\n        valid_files.append(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:54:59.090005Z","iopub.execute_input":"2025-11-02T13:54:59.090356Z","iopub.status.idle":"2025-11-02T13:55:01.765124Z","shell.execute_reply.started":"2025-11-02T13:54:59.090327Z","shell.execute_reply":"2025-11-02T13:55:01.763945Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random.seed(0)\nselected = random.sample(valid_files, min(5, len(valid_files))) # Take 5 random\n\nfig, axes = plt.subplots(len(selected), 4, figsize=(16, 4 * len(selected)))\nif len(selected) == 1:\n    axes = [axes]\n\nfor idx, file in enumerate(selected):\n    base_name = os.path.splitext(file)[0]\n    img_path = os.path.join(FORGED_DIR, file)\n    mask_path = os.path.join(MASKS_DIR, f\"{base_name}.npy\")\n\n    # Uploading an image\n    image = Image.open(img_path).convert('RGB')\n    image_np = np.array(image)\n    h_orig, w_orig = image_np.shape[:2]\n    \n    # Loading the GT mask\n    gt_mask = np.load(mask_path)\n    if gt_mask.ndim == 3:\n        if gt_mask.shape[0] <= 10:\n            gt_mask = np.any(gt_mask, axis=0)\n        elif gt_mask.shape[-1] <= 10:\n            gt_mask = np.any(gt_mask, axis=-1)\n        else:\n            gt_mask = gt_mask[0] if gt_mask.shape[0] <= 10 else gt_mask[:, :, 0]\n    gt_mask = (gt_mask > 0).astype(np.uint8)\n    \n    # Model prediction\n    with torch.no_grad():\n        results = model(image, imgsz=512, verbose=False)\n    pred_mask = np.zeros((h_orig, w_orig), dtype=np.uint8)\n    \n    if results[0].masks is not None:\n        masks = results[0].masks.data.cpu().numpy()\n        confs = results[0].boxes.conf.cpu().numpy()\n        CONF_THRESH = 0.6\n        for i, conf in enumerate(confs):\n            if conf > CONF_THRESH:\n                resized = cv2.resize(masks[i], (w_orig, h_orig), interpolation=cv2.INTER_NEAREST)\n                pred_mask = np.logical_or(pred_mask, resized > 0.5)\n\n    pred_mask = pred_mask.astype(np.uint8)\n\n    # Overlay GT\n    overlay_gt = image_np.copy()\n    overlay_gt[gt_mask == 1] = [0, 255, 0]  # green\n\n    # Overlay Pred\n    overlay_pred = image_np.copy()\n    overlay_pred[pred_mask == 1] = [255, 0, 0]  # red\n\n    # Отображение\n    axes[idx][0].imshow(image_np)\n    axes[idx][0].set_title(\"Original\")\n    axes[idx][0].axis(\"off\")\n\n    axes[idx][1].imshow(gt_mask, cmap='gray')\n    axes[idx][1].set_title(\"Ground Truth Mask\")\n    axes[idx][1].axis(\"off\")\n\n    axes[idx][2].imshow(pred_mask, cmap='gray')\n    axes[idx][2].set_title(\"Predicted Mask\")\n    axes[idx][2].axis(\"off\")\n\n    axes[idx][3].imshow(overlay_gt)\n    axes[idx][3].imshow(overlay_pred, alpha=0.5)  # let's put a predicate on top of GT\n    axes[idx][3].set_title(\"Overlay: GT (green) + Pred (red)\")\n    axes[idx][3].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T13:55:01.766295Z","iopub.execute_input":"2025-11-02T13:55:01.766686Z","iopub.status.idle":"2025-11-02T13:55:26.681381Z","shell.execute_reply.started":"2025-11-02T13:55:01.766653Z","shell.execute_reply":"2025-11-02T13:55:26.680102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}