{"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":113558,"databundleVersionId":14174843,"sourceType":"competition"},{"sourceId":13659136,"sourceType":"datasetVersion","datasetId":8670261}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"\n\n* **SIFD:** **S**cientific **I**mage **F**orgery **D**etection (The problem domain).\n* **U-NET:** The **Deep Learning Architecture** used for pixel-level segmentation.\n* **Tversky:** The **Specialized Loss Function** used to combat extreme class imbalance.\n* **CTF:** **Continuous Fine-Tuning** (The optimization strategy used for iterative error correction).\n* **Final:** Signifies that this file contains the **best, optimized weights** from the completed pipeline.","metadata":{}},{"cell_type":"code","source":"!nvidia-smi","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:50:22.543391Z","iopub.execute_input":"2025-11-08T16:50:22.544063Z","iopub.status.idle":"2025-11-08T16:50:22.764713Z","shell.execute_reply.started":"2025-11-08T16:50:22.543992Z","shell.execute_reply":"2025-11-08T16:50:22.763978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport tensorflow as tf\n\n# 1. Set CUDA_VISIBLE_DEVICES to \"-1\" to hide all GPUs from CUDA.\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\"\n\n# 2. Verify and disable any logical devices that have already been created.\ntry:\n    # Get a list of all physical GPU devices visible to TensorFlow\n    physical_devices = tf.config.list_physical_devices('GPU')\n    if physical_devices:\n        # Hide all physical GPU devices from TensorFlow's runtime\n        tf.config.set_visible_devices([], 'GPU')\n        print(\"✅ Successfully disabled physical GPUs for submission.\")\nexcept Exception as e:\n    # Catching the case where list_physical_devices fails if TF is in a bad state\n    print(f\"Warning: Could not configure GPU visibility. Error: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:51:09.105024Z","iopub.execute_input":"2025-11-08T16:51:09.105615Z","iopub.status.idle":"2025-11-08T16:51:09.110209Z","shell.execute_reply.started":"2025-11-08T16:51:09.105592Z","shell.execute_reply":"2025-11-08T16:51:09.109560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\nfrom tqdm.auto import tqdm\nimport warnings\nfrom warnings import filterwarnings\n\nfilterwarnings('ignore') # Suppress warnings\n\n# --- CONFIGURATION (from the original notebook) ---\nTARGET_SIZE = 256\nTRAIN_ROOT = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images\"\nMASK_ROOT = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks\"\n\n\n# Replicate compute_ela for feature analysis\ndef compute_ela(img_path, quality=95, scale=10):\n    # ... (omitted for brevity, assume the original function is available)\n    # The original notebook's ELA function is used here.\n    img = cv2.imread(img_path)\n    if img is None or img.size == 0:\n        try:\n            img_data = np.load(img_path)\n            if img_data.ndim == 3: img = cv2.cvtColor(img_data, cv2.COLOR_RGB2BGR)\n            elif img_data.ndim == 2: img = cv2.cvtColor(img_data, cv2.COLOR_GRAY2BGR)\n        except Exception: return np.zeros((TARGET_SIZE, TARGET_SIZE), dtype=np.float32)\n\n    if img is None or img.size == 0:\n        return np.zeros((TARGET_SIZE, TARGET_SIZE), dtype=np.float32)\n\n    img_resized = cv2.resize(img, (TARGET_SIZE, TARGET_SIZE))\n    temp_path = f\"/tmp/temp_ela_{os.path.basename(img_path)}.jpg\" # Simplified temp_path\n    try:\n        # Use a consistent quality setting (95)\n        cv2.imwrite(temp_path, img_resized, [cv2.IMWRITE_JPEG_QUALITY, quality])\n        compressed_img = cv2.imread(temp_path)\n        if compressed_img is None: return np.zeros((TARGET_SIZE, TARGET_SIZE), dtype=np.float32)\n        error = np.abs(img_resized.astype(np.float32) - compressed_img.astype(np.float32))\n        ela_feature_2d = np.mean(error, axis=2) * scale # Scale by 10 as in the notebook\n    finally:\n        if os.path.exists(temp_path): os.remove(temp_path)\n    return cv2.resize(ela_feature_2d, (TARGET_SIZE, TARGET_SIZE), interpolation=cv2.INTER_LINEAR).astype(np.float32)\n\n# Load the filtered DataFrame (assuming the prior EDA cell's 'eda_df' is available or recreate it)\ndata_list = []\nfor root, _, files in os.walk(TRAIN_ROOT):\n    for f in files:\n        valid_extensions = ('.png', '.jpg', '.jpeg', '.tif', '.tiff', '.npy')\n        if f.lower().endswith(valid_extensions) and 'forged' in root.lower():\n            case_id = os.path.splitext(f)[0]\n            mask_path = os.path.join(MASK_ROOT, f\"{case_id}.npy\")\n            if os.path.exists(mask_path):\n                data_list.append({'img_path': os.path.join(root, f), 'mask_path': mask_path})\neda_df = pd.DataFrame(data_list)\n\nprint(\"--- Starting Advanced EDA (Imbalance & Feature Check) ---\")\n\nif eda_df.empty:\n    print(\"🛑 EDA Skipped: Data frame is empty.\")\nelse:\n    total_pixels = 0\n    forgery_pixels = 0\n    ela_values, rgb_means = [], []\n\n    # Process only the first 50 images to speed up ELA computation for EDA\n    for index, row in tqdm(eda_df.head(50).iterrows(), total=len(eda_df.head(50)), desc=\"Processing samples\"):\n        try:\n            # 1. Image and Mask Load\n            rgb_image = cv2.cvtColor(cv2.imread(row['img_path']), cv2.COLOR_BGR2RGB)\n            if rgb_image is None or rgb_image.size == 0: continue\n\n            mask = np.load(row['mask_path'])\n            if mask.ndim > 2: mask = mask[:, :, 0]\n\n            # 2. Imbalance Check (Use original sizes for best estimate)\n            h, w = rgb_image.shape[:2]\n            total_pixels += h * w\n            forgery_pixels += np.sum(mask > 0)\n\n            # 3. ELA Feature Check (Use 256x256 resized data)\n            ela_feature = compute_ela(row['img_path'])\n            ela_values.extend(ela_feature.flatten())\n\n            # RGB feature check (resize/normalize similar to training)\n            rgb_resized = cv2.resize(rgb_image, (TARGET_SIZE, TARGET_SIZE)) / 255.0\n            rgb_means.extend(rgb_resized.mean(axis=2).flatten())\n\n        except Exception as e:\n            # print(f\"Warning: Could not process {row['img_path']}: {e}\")\n            continue\n\n    # --- Analysis 1: Imbalance Ratio ---\n    if total_pixels > 0:\n        imbalance_ratio = (forgery_pixels / total_pixels) * 100\n        print(f\"\\n--- Imbalance Ratio (Forged Pixels) ---\")\n        print(f\"Total Pixels Sampled: {total_pixels:,}\")\n        print(f\"Forged Pixels Sampled: {forgery_pixels:,}\")\n        print(f\"Forgery Imbalance Ratio: **{imbalance_ratio:.2f}%** (Positive Class)\")\n\n    # --- Analysis 2: ELA Feature Distribution vs. RGB ---\n    if ela_values:\n        ela_values = np.array(ela_values)\n        rgb_means = np.array(rgb_means)\n\n        print(f\"\\n--- ELA Feature Distribution (Scaled by 10) ---\")\n        print(f\"ELA Feature Mean: {np.mean(ela_values):.4f}\")\n        print(f\"ELA Feature Std Dev: {np.std(ela_values):.4f}\")\n        print(f\"RGB Mean (Normalized): {np.mean(rgb_means):.4f}\")\n\n        plt.figure(figsize=(12, 5))\n        plt.hist(ela_values, bins=50, alpha=0.6, label='ELA Feature (Scaled)', color='red')\n        plt.title('Distribution of ELA Feature Values')\n        plt.xlabel('ELA Value (0 to ~2550)')\n        plt.ylabel('Frequency')\n        plt.legend()\n        plt.show()\n\n        # This histogram helps visualize if ELA is predominantly zero or clustered.\n\nprint(\"\\n--- Advanced EDA Complete ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:50:36.924283Z","iopub.execute_input":"2025-11-08T16:50:36.924710Z","iopub.status.idle":"2025-11-08T16:50:49.498461Z","shell.execute_reply.started":"2025-11-08T16:50:36.924682Z","shell.execute_reply":"2025-11-08T16:50:49.497500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\nimport os\n\n# Define the file path\nTEST_IMAGE_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images/45.png\"\n\nprint(f\"Attempting to load image: {TEST_IMAGE_PATH}\")\n\nif not os.path.exists(TEST_IMAGE_PATH):\n    print(\"🛑 ERROR: The file path was not found. Please ensure the Kaggle competition data is mounted correctly.\")\nelse:\n    # Load the image using OpenCV (loads as BGR)\n    img = cv2.imread(TEST_IMAGE_PATH)\n\n    if img is None:\n        print(\"🛑 ERROR: Could not read the image file.\")\n    else:\n        # Convert the image from BGR (OpenCV default) to RGB (Matplotlib default)\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        # Plot the image\n        plt.figure(figsize=(10, 8))\n        plt.imshow(img_rgb)\n        plt.title(f\"Test Image 45 (Dimensions: {img.shape[0]}x{img.shape[1]})\")\n        plt.axis('off') # Hide axes for a cleaner image view\n        plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T16:50:49.500241Z","iopub.execute_input":"2025-11-08T16:50:49.500488Z","iopub.status.idle":"2025-11-08T16:50:49.996523Z","shell.execute_reply.started":"2025-11-08T16:50:49.500469Z","shell.execute_reply":"2025-11-08T16:50:49.995703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport csv\nimport warnings\nimport gc\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, backend as K\nfrom tensorflow.keras.utils import get_custom_objects\nimport cv2\nimport logging\nimport sys\nfrom tqdm.auto import tqdm\n\n# --- FINAL SUBMISSION CONFIGURATION ---\nIMAGE_SIZE = 256\nMODEL_INPUT_CHANNELS = 6\n# Assuming the CTF7 file is accessible via this path\nFINAL_MODEL_PATH = \"/kaggle/input/rluc-sfic-st/model_ctf7_output_Final_Balance.weights.h5\"\nOUTPUT_FILENAME = \"submission.csv\"\nFIXED_THRESHOLD = 0.35\nMIN_FORGERY_AREA = 64\nTversky_BETA = 0.55 \nalpha = 0.45\n\n# Kaggle Paths\nKAGGLEHUB_PATH = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nTEST_IMAGE_ROOT = os.path.join(KAGGLEHUB_PATH, \"test_images\")\nSAMPLE_SUBMISSION_FILE = os.path.join(KAGGLEHUB_PATH, \"sample_submission.csv\")\n\n# --- CORE FUNCTIONS ---\ndef tversky_loss(y_true, y_pred, alpha=alpha, beta=Tversky_BETA, smooth=1e-7):\n    y_true_f = K.flatten(y_true); y_pred_f = K.flatten(y_pred)\n    TP = K.sum(y_true_f * y_pred_f); FP = K.sum((1 - y_true_f) * y_pred_f)\n    FN = K.sum(y_true_f * (1 - y_pred_f))\n    tversky_index = (TP + smooth) / (TP + alpha * FP + beta * FN + smooth)\n    return 1 - tversky_index\n\ndef get_forgery_features_from_data(img_grayscale_data):\n    img = img_grayscale_data.astype(np.uint8); blur = cv2.GaussianBlur(img, (5, 5), 0)\n    residual = img.astype(np.float32) - blur.astype(np.float32)\n    residual = cv2.resize(residual, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_LINEAR)\n    residual_min, residual_max = residual.min(), residual.max()\n    residual = (residual - residual_min) / (residual_max - residual_min + 1e-7)\n    return np.stack([residual]*3, axis=-1).astype(np.float32)\n\ndef build_single_stream_unet(input_shape=(IMAGE_SIZE, IMAGE_SIZE, MODEL_INPUT_CHANNELS)):\n    input_combined = layers.Input(shape=input_shape, name='combined_input')\n    conv1 = layers.Conv2D(32, 3, activation='relu', padding='same')(input_combined); conv1 = layers.Conv2D(32, 3, activation='relu', padding='same')(conv1)\n    pool1 = layers.MaxPooling2D((2, 2))(conv1)\n    conv2 = layers.Conv2D(64, 3, activation='relu', padding='same')(pool1); conv2 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv2)\n    pool2 = layers.MaxPooling2D((2, 2))(conv2)\n    bridge = layers.Conv2D(128, 3, activation='relu', padding='same')(pool2); bridge = layers.Conv2D(128, 3, activation='relu', padding='same')(bridge)\n    up3 = layers.Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(bridge)\n    merge3 = layers.concatenate([up3, conv2]); conv3 = layers.Conv2D(64, 3, activation='relu', padding='same')(merge3); conv3 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv3)\n    up4 = layers.Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(conv3)\n    merge4 = layers.concatenate([up4, conv1]); conv4 = layers.Conv2D(32, 3, activation='relu', padding='same')(merge4); conv4 = layers.Conv2D(32, 3, activation='relu', padding='same')(conv4)\n    output = layers.Conv2D(1, 1, activation='sigmoid', padding='same')(conv4)\n    model = models.Model(inputs=input_combined, outputs=output)\n    return model\n\ntf.keras.utils.get_custom_objects().update({'tversky_loss': tversky_loss})\n\ndef rle_encode(mask):\n    if mask.sum() == 0: return \"authentic\"\n    pixels = mask.T.flatten(); pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)\n\ndef create_test_df_robust(test_image_root, sample_submission_path):\n    master_df = pd.read_csv(sample_submission_path); master_df['case_id'] = master_df['case_id'].astype(str)\n    present_files = {}\n    if os.path.exists(test_image_root):\n        for root, _, files in os.walk(test_image_root):\n            for f in files:\n                case_id = os.path.splitext(f)[0]\n                if f.lower().endswith(('.png', '.jpg', '.jpeg', '.tif', '.tiff', '.npy')) and case_id.isdigit():\n                    present_files[case_id] = os.path.join(root, f)\n    master_df['img_path'] = master_df['case_id'].map(present_files).fillna('MISSING_FILE')\n    return master_df[master_df['img_path'] != 'MISSING_FILE'][['case_id', 'img_path']].reset_index(drop=True)\n\ndef run_submission_inference(unet_model, test_df, fixed_threshold, min_forgery_area):\n    results = []\n    # Set GPU/CPU device based on environment check\n    with tf.device('/cpu:0'):\n        for index, row in tqdm(test_df.iterrows(), total=len(test_df), desc=\"Generating Submission\"):\n            case_id = str(row['case_id']); img_path = row['img_path']\n            gc.collect(); K.clear_session()\n\n            img_bgr = None\n            try:\n                img_bgr = cv2.imread(img_path)\n                if img_bgr is None or img_bgr.size == 0:\n                    try:\n                        img_data = np.load(img_path)\n                        if img_data.ndim == 3: img_bgr = cv2.cvtColor(img_data, cv2.COLOR_RGB2BGR)\n                        elif img_data.ndim == 2: img_bgr = cv2.cvtColor(img_data, cv2.COLOR_GRAY2BGR)\n                    except Exception: results.append({'case_id': case_id, 'annotation': 'authentic'}); continue\n                    if img_bgr is None or img_bgr.size == 0: results.append({'case_id': case_id, 'annotation': 'authentic'}); continue\n\n                img_rgb_orig = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB);\n                img_gray_orig = cv2.cvtColor(img_rgb_orig, cv2.COLOR_RGB2GRAY)\n                original_shape = img_rgb_orig.shape[:2]\n\n                X1_rgb = cv2.resize(img_rgb_orig, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_LINEAR) / 255.0\n                X2_features = get_forgery_features_from_data(img_gray_orig)\n                X_combined = np.concatenate([X1_rgb, X2_features], axis=-1)\n\n                input_combined = np.expand_dims(X_combined, axis=0)\n\n                # Use standard Keras predict() on the CPU\n                model_output = unet_model.predict(input_combined, verbose=0)\n                output_prob = model_output[0, :, :, 0]\n\n                # Use the submission FIXED_THRESHOLD (0.35)\n                final_mask_resized = (output_prob > fixed_threshold).astype(np.uint8)\n                clean_mask_resized = np.zeros_like(final_mask_resized)\n\n                num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(final_mask_resized, 4, cv2.CV_32S)\n\n                for label in range(1, num_labels):\n                    area = stats[label, cv2.CC_STAT_AREA]\n                    if area >= min_forgery_area:\n                        clean_mask_resized[labels == label] = 1\n\n                final_mask = cv2.resize(clean_mask_resized, (original_shape[1], original_shape[0]), interpolation=cv2.INTER_NEAREST)\n                rle_annotation = rle_encode(final_mask); results.append({'case_id': case_id, 'annotation': rle_annotation})\n\n            except Exception as e:\n                results.append({'case_id': case_id, 'annotation': 'authentic'}); continue\n\n    return pd.DataFrame(results)\n\n# --- 3. FINAL EXECUTION BLOCK FOR INFERENCE ---\nif __name__ == \"__main__\":\n\n    print(\"\\n--- Starting FINAL SUBMISSION INFERENCE ---\")\n\n    # 1. Load Model (CTF7 Final Checkpoint)\n    model = build_single_stream_unet((IMAGE_SIZE, IMAGE_SIZE, MODEL_INPUT_CHANNELS))\n    tf.keras.utils.get_custom_objects().update({'tversky_loss': tversky_loss})\n    model.compile(optimizer='adam', loss=tversky_loss, metrics=['accuracy'])\n\n    try:\n        if not os.path.exists(FINAL_MODEL_PATH):\n             raise FileNotFoundError(f\"Final model weights not found at: {FINAL_MODEL_PATH}.\")\n        \n        # Suppress UserWarning on load_weights\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\", UserWarning)\n            model.load_weights(FINAL_MODEL_PATH)\n        \n        print(f\"✅ Loaded Final CTF Model weights: {FINAL_MODEL_PATH}\")\n    except FileNotFoundError as e:\n        print(f\"🛑 FATAL Error: {e}. You must successfully run the CTF7 training step to generate the final weights.\")\n        sys.exit(1)\n    except Exception as e:\n        print(f\"🛑 FATAL Error loading final weights: {e}. Aborting submission.\")\n        sys.exit(1)\n\n    # 2. Generate Submission File for Test Data\n    print(\"\\n--- Generating Kaggle Submission File ---\")\n    test_df = create_test_df_robust(TEST_IMAGE_ROOT, SAMPLE_SUBMISSION_FILE)\n\n    if test_df.empty:\n        submission_df = pd.DataFrame(columns=['case_id', 'annotation'])\n    else:\n        print(f\"Processing {len(test_df)} test case(s)...\\n\")\n        results_df = run_submission_inference(model, test_df, FIXED_THRESHOLD, MIN_FORGERY_AREA)\n        submission_df = pd.read_csv(SAMPLE_SUBMISSION_FILE)[['case_id']].astype(str)\n        submission_df = submission_df.merge(results_df, on='case_id', how='left')\n        submission_df['annotation'] = submission_df['annotation'].fillna('authentic')\n        submission_df = submission_df[['case_id', 'annotation']].sort_values('case_id').reset_index(drop=True)\n\n    # 3. Write Final CSV (Correct RLE Formatting)\n    with open(OUTPUT_FILENAME, \"w\", newline='') as f:\n        # csv.writer will handle the external double quotes for us because of QUOTE_MINIMAL\n        writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)\n        writer.writerow(['case_id', 'annotation'])\n\n        for _, row in submission_df.iterrows():\n            annotation = row['annotation']\n\n            if annotation.lower() == 'authentic':\n                writer.writerow([row['case_id'], annotation])\n            else:\n                # CRITICAL FIX: Generate the exact comma-separated string required inside the brackets.\n                \n                # 1. Split the space-separated numbers (e.g., \"442080 34 442384 40\")\n                rle_list = annotation.split(' ')\n                \n                # 2. Join the list using \", \" (e.g., \"442080, 34, 442384, 40\")\n                comma_separated_rle = \", \".join(rle_list)\n                \n                # 3. Wrap in brackets. The csv.writer handles the external double quotes.\n                full_rle_string = f\"[{comma_separated_rle}]\"\n                \n                writer.writerow([row['case_id'], full_rle_string])\n\n    print(f\"\\n✅ FINAL SUBMISSION CREATED: {OUTPUT_FILENAME} with {len(submission_df)} total rows. Please submit this file.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T18:08:03.195640Z","iopub.execute_input":"2025-11-08T18:08:03.196294Z","iopub.status.idle":"2025-11-08T18:08:04.208138Z","shell.execute_reply.started":"2025-11-08T18:08:03.196267Z","shell.execute_reply":"2025-11-08T18:08:04.207335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\ndef validate_and_print_rle(submission_df):\n    \"\"\"\n    Validates RLE output structure and prints debugging info,\n    including the total count of RLE segment pairs.\n    \"\"\"\n    print(\"\\n--- RLE Output Validation Check ---\")\n\n    # Analyze the annotations\n    authentic_count = submission_df['annotation'].apply(lambda x: x.lower().strip().replace('[]', '') == 'authentic').sum()\n    rle_rows = submission_df[submission_df['annotation'].apply(lambda x: x.lower().strip().replace('[]', '') != 'authentic')]\n\n    print(f\"Total Submissions: {len(submission_df)}\")\n    print(f\"Authentic (No Forgery) Count: {authentic_count}\")\n    print(f\"RLE Annotated (Forged) Count: {len(rle_rows)}\")\n\n    # --- NEW: Calculate Total Segment Pairs ---\n    total_rle_elements = 0\n\n    def count_rle_elements(rle_string):\n        nonlocal total_rle_elements\n        rle_string = rle_string.strip().strip('[]')\n        if not rle_string or rle_string.lower() == 'authentic':\n            return True\n        try:\n            elements = rle_string.replace(',', ' ').split()\n            num_elements = len(elements)\n            if num_elements % 2 == 0:\n                total_rle_elements += num_elements\n            return num_elements % 2 == 0\n        except:\n            return False\n\n    rle_check = rle_rows['annotation'].apply(count_rle_elements)\n\n    if rle_check.all():\n        total_pairs = total_rle_elements // 2\n        print(f\"✅ RLE Structure: All {len(rle_rows)} RLE strings are structurally valid.\")\n        print(f\"Total Segment Pairs Detected: {total_pairs} (Indicates the complexity of the forgery patterns).\")\n    else:\n        bad_rle_count = len(rle_rows) - rle_check.sum()\n        total_pairs = 0\n        print(f\"🛑 RLE ERROR: Found {bad_rle_count} RLE strings with invalid structure.\")\n\n    return total_pairs\n\n# --- Execution ---\ntry:\n    # Load the submission file\n    submission_df = pd.read_csv(\"submission.csv\")\n\n    # Perform validation\n    TS=validate_and_print_rle(submission_df)\n\n    # Print the content for confirmation\n    #print(\"\\n--- submission.csv Content ---\")\n    #print(submission_df.to_string(index=False))\n\n    #if not submission_df.empty:\n    #    test_case_result = submission_df.iloc[0]['annotation']\n    #   print(f\"\\nModel Prediction for Test Case {submission_df.iloc[0]['case_id']}: The image is classified as {test_case_result}.\")\n\nexcept FileNotFoundError:\n    print(\"Error: submission.csv not found. Please ensure the inference code was run successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-08T18:08:23.572361Z","iopub.execute_input":"2025-11-08T18:08:23.572937Z","iopub.status.idle":"2025-11-08T18:08:23.583859Z","shell.execute_reply.started":"2025-11-08T18:08:23.572910Z","shell.execute_reply":"2025-11-08T18:08:23.583273Z"}},"outputs":[],"execution_count":null}]}