{"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"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!nvidia-smi","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T02:12:27.568393Z","iopub.execute_input":"2025-11-03T02:12:27.568911Z","iopub.status.idle":"2025-11-03T02:12:27.794233Z","shell.execute_reply.started":"2025-11-03T02:12:27.568887Z","shell.execute_reply":"2025-11-03T02:12:27.793556Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport warnings\nimport logging\n\n# Note: The Python logging code is kept, but the primary suppression relies on the shell command above.\n\n# 1. Suppress the Python logging framework (ensures Keras logs are also silenced)\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nos.environ[\"CUDA_DEVICE_ORDER\"] = \"PCI_BUS_ID\" \nlogging.getLogger('tensorflow').setLevel(logging.FATAL) \nwarnings.filterwarnings(\"ignore\", category=UserWarning)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T02:12:34.614660Z","iopub.execute_input":"2025-11-03T02:12:34.615158Z","iopub.status.idle":"2025-11-03T02:12:34.619424Z","shell.execute_reply.started":"2025-11-03T02:12:34.615133Z","shell.execute_reply":"2025-11-03T02:12:34.618642Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- CONFIGURATION (FINAL AGGRESSIVE LOG SUPPRESSION) ---\n\nCOMPETITION_SLUG = \"recodai-luc-scientific-image-forgery-detection\"\nTRAIN_ROOT = f\"/kaggle/input/{COMPETITION_SLUG}/train_images/forged\"\nMASK_ROOT = f\"/kaggle/input/{COMPETITION_SLUG}/train_masks\"\n\nIMAGE_SIZE = 256\nBATCH_SIZE = 16\nN_FOLDS = 1 \n# ---------------------\n\nimport os\nimport sys\nimport warnings\nimport logging\nimport numpy as np\nimport pandas as pd\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport gc\n\n# 1. CRITICAL: Redirect stderr to suppress C++ logs (E0000, I0000, cuDNN, etc.)\n# This is the most reliable method when os.environ fails.\nclass SuppressTFLogs:\n    def __enter__(self):\n        # Save original stderr\n        self.original_stderr = sys.stderr\n        # Redirect stderr to /dev/null\n        sys.stderr = open(os.devnull, 'w')\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        # Restore original stderr\n        sys.stderr = self.original_stderr\n\n# 2. Set environment variables to level 3 (FATAL)\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'\nos.environ[\"CUDA_DEVICE_ORDER\"] = \"PCI_BUS_ID\" \nlogging.getLogger('tensorflow').setLevel(logging.FATAL) \nwarnings.filterwarnings(\"ignore\", category=UserWarning) \n\n# --- Import TensorFlow within the suppressed context ---\nwith SuppressTFLogs():\n    # Only import heavy libraries inside the suppressed block\n    import tensorflow as tf\n    from tensorflow.keras import layers, models, backend as K\n    from tensorflow.keras.callbacks import Callback, EarlyStopping, ReduceLROnPlateau\n    import cv2\n    \ntf.random.set_seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-03T02:13:16.302823Z","iopub.execute_input":"2025-11-03T02:13:16.303446Z","iopub.status.idle":"2025-11-03T02:13:16.310319Z","shell.execute_reply.started":"2025-11-03T02:13:16.303422Z","shell.execute_reply":"2025-11-03T02:13:16.309596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- CONFIGURATION (FINAL CODE POST-FIX) ---\n\nCOMPETITION_SLUG = \"recodai-luc-scientific-image-forgery-detection\"\n\n# The path structure that should finally work after resolving external kernel issues:\nTRAIN_ROOT = f\"/kaggle/input/{COMPETITION_SLUG}/train_images/forged\"\nMASK_ROOT = f\"/kaggle/input/{COMPETITION_SLUG}/train_masks\"\n\nIMAGE_SIZE = 256\nBATCH_SIZE = 16\nN_FOLDS = 1 \n# ---------------------\n\nimport numpy as np\nimport pandas as pd\nimport os\nimport warnings\nimport logging\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, backend as K\nfrom tensorflow.keras.callbacks import Callback, EarlyStopping, ReduceLROnPlateau\nimport cv2\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport gc\n\ntf.random.set_seed(42)\n\n# --- 1. Utility Functions, Metrics, and Callbacks ---\n\ndef rle_encode(mask):\n    \"\"\"Encodes a binary mask using Run Length Encoding (Defined for completeness).\"\"\"\n    pixels = mask.flatten()\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] = runs[1::2] - runs[:-1:2]\n    return ' '.join(str(x) for x in runs)\n\ndef dice_coef(y_true, y_pred, smooth=1e-7):\n    y_true_f = K.flatten(y_true)\n    y_pred_f = K.flatten(y_pred)\n    intersection = K.sum(y_true_f * y_pred_f)\n    return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)\n\ndef dice_loss(y_true, y_pred):\n    return 1 - dice_coef(y_true, y_pred)\n\nclass EpochStatReporter(Callback):\n    \"\"\"Callback to report total skipped (corrupt) samples after each epoch.\"\"\"\n\n    def __init__(self, generator):\n        super().__init__()\n        self.generator = generator\n\n    def on_epoch_end(self, epoch, logs=None):\n        skipped = self.generator.skipped_count\n        log_message = f\"Epoch {epoch + 1} finished: \"\n        for k, v in logs.items():\n            log_message += f\"{k}: {v:.4f} \"\n        log_message += f\"| TOTAL SAMPLES SKIPPED: {skipped}\"\n        \n        print(\"\\n\" + \"=\"*80)\n        print(log_message)\n        print(\"=\"*80 + \"\\n\")\n\n# --- 2. Forgery Feature Extraction (Optimized) ---\n\ndef get_forgery_features(image_id):\n    \"\"\"Generates a Noise Residual feature map (Stream 2 input).\"\"\"\n    img_path = os.path.join(TRAIN_ROOT, f'{image_id}.png') \n    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) \n    \n    if img is None:\n        return np.zeros((IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.float32)\n\n    blur = cv2.GaussianBlur(img, (5, 5), 0)\n    residual = img.astype(np.float32) - blur.astype(np.float32)\n    \n    residual = cv2.resize(residual, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_LINEAR)\n    residual = (residual - residual.min()) / (residual.max() - residual.min() + 1e-7)\n    \n    return np.stack([residual]*3, axis=-1).astype(np.float32)\n\n# --- 3. Custom Dual-Stream Data Generator (WITH SKIPPED COUNT) ---\n\nclass DualStreamDataGenerator(tf.keras.utils.Sequence):\n    \n    def __init__(self, df, batch_size=16, shuffle=True, **kwargs):\n        super().__init__(**kwargs) \n        self.df = df\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n        self.on_epoch_end()\n        self.skipped_count = 0 \n\n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.df))\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n        self.skipped_count = 0 \n\n    def __len__(self):\n        return int(np.floor(len(self.df) / self.batch_size))\n\n    def __getitem__(self, index):\n        indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n        batch_df = self.df.iloc[indexes]\n        \n        temp_X1, temp_X2, temp_Y = [], [], []\n\n        for row in batch_df.itertuples():\n            image_id = row.id\n            img_path = os.path.join(TRAIN_ROOT, f'{image_id}.png') \n            mask_path = os.path.join(MASK_ROOT, f'{image_id}.npy') \n            \n            # --- Load Image and Features ---\n            try:\n                # RGB Image\n                img = cv2.imread(img_path)\n                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n                X1_sample = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_LINEAR) / 255.0\n                \n                # Feature Map\n                X2_sample = get_forgery_features(image_id)\n                \n            except Exception as e:\n                self.skipped_count += 1\n                continue \n\n            # --- Load and Validate Mask (CRITICAL FIXES) ---\n            try:\n                mask = np.load(mask_path)\n                \n                # Squeeze and validate dimensions\n                mask = np.squeeze(mask)\n                if mask.ndim < 2 or 0 in mask.shape:\n                    self.skipped_count += 1\n                    continue \n\n                # Resize the valid mask\n                mask_resized = cv2.resize(mask, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_NEAREST)\n                \n                # Reshape to the exact required target shape (256, 256, 1)\n                Y_sample = mask_resized.reshape(IMAGE_SIZE, IMAGE_SIZE, 1).astype(np.float32)\n            \n            except Exception as e:\n                # Handles mask corruption\n                self.skipped_count += 1\n                continue \n            \n            # Add valid samples to temporary lists\n            temp_X1.append(X1_sample)\n            temp_X2.append(X2_sample)\n            temp_Y.append(Y_sample)\n            \n        # --- Final Batch Construction (Handling Skips) ---\n        \n        if not temp_X1:\n             # If the entire batch was corrupt, return a placeholder batch (TUPLE format)\n             placeholder_x1 = np.zeros((self.batch_size, IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.float32)\n             placeholder_x2 = np.zeros((self.batch_size, IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.float32)\n             placeholder_y = np.zeros((self.batch_size, IMAGE_SIZE, IMAGE_SIZE, 1), dtype=np.float32)\n             \n             return (placeholder_x1, placeholder_x2), placeholder_y\n\n        # Pad the batch with the last valid sample if any were skipped\n        while len(temp_X1) < self.batch_size:\n            temp_X1.append(temp_X1[-1])\n            temp_X2.append(temp_X2[-1])\n            temp_Y.append(temp_Y[-1])\n            \n        # CRITICAL FIX: Return a TUPLE for the inputs (X1, X2)\n        return (np.array(temp_X1), np.array(temp_X2)), np.array(temp_Y)\n\n# --- 4. Dual-Stream U-Net Model ---\ndef build_dual_stream_unet(input_shape):\n    input_rgb = layers.Input(shape=input_shape, name='rgb_input')\n    conv_rgb = models.Sequential([\n        layers.Conv2D(32, 3, activation='relu', padding='same'), layers.MaxPooling2D(),\n        layers.Conv2D(64, 3, activation='relu', padding='same')], name='rgb_stream')(input_rgb)\n    input_feat = layers.Input(shape=input_shape, name='feature_input')\n    conv_feat = models.Sequential([\n        layers.Conv2D(32, 3, activation='relu', padding='same'), layers.MaxPooling2D(),\n        layers.Conv2D(64, 3, activation='relu', padding='same')], name='feature_stream')(input_feat)\n    merged = layers.concatenate([conv_rgb, conv_feat])\n    up1 = layers.UpSampling2D(size=(2, 2))(merged)\n    conv_final = layers.Conv2D(128, 3, activation='relu', padding='same')(up1)\n    output = layers.Conv2D(1, 1, activation='sigmoid', padding='same')(conv_final)\n    model = models.Model(inputs=[input_rgb, input_feat], outputs=output)\n    return model\n\n# --- 5. Training Loop (Execution) ---\n\nall_files = glob(os.path.join(TRAIN_ROOT, '*.png'))\ndf = pd.DataFrame([os.path.basename(f).replace('.png', '') for f in all_files], columns=['id'])\n\n# --- Final Checks ---\nif len(df) == 0:\n    raise RuntimeError(f\"FATAL: The path '{TRAIN_ROOT}' is empty. Kernel environment failed to load data.\")\n\nif len(df) < BATCH_SIZE:\n    raise RuntimeError(f\"FATAL: Data size ({len(df)}) is less than BATCH_SIZE ({BATCH_SIZE}). Cannot train.\")\n\n# Initialize Generator and Model\ntrain_gen = DualStreamDataGenerator(df, batch_size=BATCH_SIZE) \nmodel = build_dual_stream_unet((IMAGE_SIZE, IMAGE_SIZE, 3))\n\nmodel.compile(optimizer='adam', loss=dice_loss, metrics=[dice_coef, 'accuracy'])\n\n# Implement the stability callbacks\nreduce_lr = ReduceLROnPlateau(\n    monitor='loss', \n    factor=0.5, \n    patience=3, \n    min_lr=1e-6, \n    verbose=1\n)\n\nearly_stop = EarlyStopping(\n    monitor='loss', \n    patience=5, \n    restore_best_weights=True, \n    verbose=1\n)\n\n# Instantiate the custom callback\nstat_reporter = EpochStatReporter(train_gen)\ncallbacks = [stat_reporter, reduce_lr, early_stop]\n\nprint(f\"Generator will produce {len(train_gen)} batches per epoch.\")\n\nmodel.fit(train_gen, epochs=100, verbose=0, callbacks=callbacks) \n\n# Save weights to the temporary directory for Part 2 inference\nmodel.save_weights('/tmp/model_new_scratch.weights.h5')\n\n# Final cleanup\ndel model; del train_gen; gc.collect()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-03T02:13:53.444152Z","iopub.execute_input":"2025-11-03T02:13:53.444722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /tmp/ ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:26:07.134492Z","iopub.execute_input":"2025-11-02T21:26:07.134815Z","iopub.status.idle":"2025-11-02T21:26:07.304878Z","shell.execute_reply.started":"2025-11-02T21:26:07.134793Z","shell.execute_reply":"2025-11-02T21:26:07.304106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- CONFIGURATION ---\nIMAGE_SIZE = 256\nTHRESHOLD = 0.45 # Use the optimal threshold determined from Part 1 validation\nOUTPUT_FILENAME = \"submission.csv\"\n\n# PATHS:\nCOMPETITION_SLUG = \"recodai-luc-scientific-image-forgery-detection\"\nTEST_ROOT = f\"/kaggle/input/{COMPETITION_SLUG}/test_images\" \nSAMPLE_SUBMISSION_FILE = f\"/kaggle/input/{COMPETITION_SLUG}/sample_submission.csv\"\n# Model path must include the correct Keras 3 extension:\nmodel_path = \"/tmp/model_new_scratch.weights.h5\" \n\n# ----------------------\n\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, backend as K\nimport os\nimport cv2\nimport gc\nfrom tqdm.notebook import tqdm\n\ntf.get_logger().setLevel('ERROR')\n\n# --- 1. Utility Functions and Model Definition (CRITICAL: Must match Part 1) ---\n\ndef rle_encode(mask):\n    \"\"\"Encodes a binary mask using Run Length Encoding (REQUIRED FOR SUBMISSION).\"\"\"\n    pixels = mask.flatten()\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] = runs[1::2] - runs[:-1:2]\n    return ' '.join(str(x) for x in runs)\n\ndef get_forgery_features(image_id):\n    \"\"\"\n    Generates the Noise Residual feature map (MUST match Part 1 exactly).\n    The path is set for the test root.\n    \"\"\"\n    img_path = os.path.join(TEST_ROOT, f'{image_id}.png')\n    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) \n    \n    if img is None:\n        return np.zeros((IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.float32)\n\n    blur = cv2.GaussianBlur(img, (5, 5), 0)\n    residual = img.astype(np.float32) - blur.astype(np.float32)\n    \n    residual = cv2.resize(residual, (IMAGE_SIZE, IMAGE_SIZE), interpolation=cv2.INTER_LINEAR)\n    residual = (residual - residual.min()) / (residual.max() - residual.min() + 1e-7)\n    \n    return np.stack([residual]*3, axis=-1).astype(np.float32)\n\ndef build_dual_stream_unet(input_shape):\n    \"\"\"Model architecture must be identical to Part 1.\"\"\"\n    input_rgb = layers.Input(shape=input_shape, name='rgb_input')\n    conv_rgb = models.Sequential([\n        layers.Conv2D(32, 3, activation='relu', padding='same'), layers.MaxPooling2D(),\n        layers.Conv2D(64, 3, activation='relu', padding='same')], name='rgb_stream')(input_rgb)\n    input_feat = layers.Input(shape=input_shape, name='feature_input')\n    conv_feat = models.Sequential([\n        layers.Conv2D(32, 3, activation='relu', padding='same'), layers.MaxPooling2D(),\n        layers.Conv2D(64, 3, activation='relu', padding='same')], name='feature_stream')(input_feat)\n    merged = layers.concatenate([conv_rgb, conv_feat])\n    up1 = layers.UpSampling2D(size=(2, 2))(merged)\n    conv_final = layers.Conv2D(128, 3, activation='relu', padding='same')(up1)\n    output = layers.Conv2D(1, 1, activation='sigmoid', padding='same')(conv_final)\n    model = models.Model(inputs=[input_rgb, input_feat], outputs=output)\n    return model\n\n# --- 2. Setup and Model Loading (KEY ERROR FIX APPLIED) ---\n\nsubmission_df = pd.read_csv(SAMPLE_SUBMISSION_FILE)\n\n# --- FIX: Identify the correct ID column ---\nid_column = None\nif 'id' in submission_df.columns:\n    id_column = 'id'\nelif 'Id' in submission_df.columns:\n    id_column = 'Id'\nelif 'ImageId' in submission_df.columns:\n    id_column = 'ImageId'\nelse:\n    # Fallback to the first column (least reliable but safer than crashing)\n    id_column = submission_df.columns[0] \n    \nif id_column is None:\n    raise KeyError(\"FATAL: Could not find 'id', 'Id', or 'ImageId' column in sample_submission.csv\")\n\ntest_image_ids = submission_df[id_column].unique()\n# ----------------------------------------\n\ntry:\n    model = build_dual_stream_unet((IMAGE_SIZE, IMAGE_SIZE, 3))\n    # CRITICAL: Load the weights with the corrected extension\n    model.load_weights(model_path)\nexcept Exception as e:\n    print(f\"FATAL: Could not load model weights from {model_path}. Submission will fail.\")\n\n# --- 3. Prediction and RLE Encoding Loop (Optimized) ---\npredictions = []\nfor image_id in tqdm(test_image_ids, desc=\"Generating Predictions\"):\n    \n    img_path = os.path.join(TEST_ROOT, f'{image_id}.png')\n    \n    # 3.1 Prepare Inputs\n    img = cv2.imread(img_path)\n    \n    if img is None:\n        rle_string = 'authentic'\n    else:\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        # Prepare dual stream inputs\n        X1 = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE)) / 255.0\n        X2 = get_forgery_features(image_id) \n\n        X1_batch = np.expand_dims(X1, axis=0)\n        X2_batch = np.expand_dims(X2, axis=0)\n        \n        # 3.2 Predict Mask\n        mask_prediction = model.predict([X1_batch, X2_batch], verbose=0)[0, ..., 0] \n        \n        # 3.3 Threshold and Encode\n        binary_mask = (mask_prediction > THRESHOLD).astype(np.uint8)\n        \n        # Check if a forgery was detected\n        if binary_mask.sum() == 0:\n            rle_string = 'authentic'\n        else:\n            rle_string = rle_encode(binary_mask)\n\n    predictions.append([image_id, rle_string])\n\n# --- 4. Final Submission ---\n# The final submission output requires two columns: the ID and the prediction.\n# Note: We must use the ID column name found earlier.\nfinal_submission_df = pd.DataFrame(predictions, columns=[id_column, 'predicted'])\nfinal_submission_df.to_csv(OUTPUT_FILENAME, index=False)\n\n# Clean up memory\ndel model; gc.collect()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:44:25.247756Z","iopub.execute_input":"2025-11-02T21:44:25.248488Z","iopub.status.idle":"2025-11-02T21:44:26.174448Z","shell.execute_reply.started":"2025-11-02T21:44:25.248461Z","shell.execute_reply":"2025-11-02T21:44:26.173656Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\nfrom IPython.display import Image, display # Import necessary display tools\nimport os\n\n# Define the image path using the simplest logical structure \nIMAGE_ID = '45'\nIMAGE_PATH = f\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images/{IMAGE_ID}.png\"\nOUTPUT_FILENAME = f'test_image_{IMAGE_ID}_plot.png'\n\n# --- Image Plotting Logic ---\ntry:\n    # 1. Load the image\n    img = cv2.imread(IMAGE_PATH)\n\n    if img is None:\n        print(f\"ERROR: Image not found at the path: {IMAGE_PATH}\")\n    else:\n        # 2. Convert to RGB\n        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        # 3. Plot the image using matplotlib\n        plt.figure(figsize=(6, 6))\n        plt.imshow(img_rgb)\n        plt.title(f'Test Image {IMAGE_ID}.png (Predicted: FORGED)')\n        plt.axis('off')\n        \n        # 4. Save the figure to the working directory\n        plt.savefig(OUTPUT_FILENAME)\n        plt.close()\n        \n        # 5. Display the saved image file for immediate viewing in the notebook\n        print(f\"Image saved to: {OUTPUT_FILENAME}\")\n        display(Image(filename=OUTPUT_FILENAME)) # Force display of the saved file\n\nexcept Exception as e:\n    print(f\"An error occurred during image plotting: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:42:29.112035Z","iopub.execute_input":"2025-11-02T21:42:29.112620Z","iopub.status.idle":"2025-11-02T21:42:29.471534Z","shell.execute_reply.started":"2025-11-02T21:42:29.112601Z","shell.execute_reply":"2025-11-02T21:42:29.470746Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\ndef analyze_authenticity(submission_file='submission.csv'):\n    \"\"\"\n    Reads the submission file, identifies the single test image, and prints\n    whether the model predicted the image to be authentic or forged.\n    \"\"\"\n    try:\n        df = pd.read_csv(submission_file)\n    except FileNotFoundError:\n        print(f\"ERROR: The file '{submission_file}' was not found.\")\n        print(\"Please ensure the Part 2 notebook ran and the file is in the current directory.\")\n        return\n\n    if df.shape[0] == 0:\n        print(\"ERROR: The submission file is empty.\")\n        return\n\n    # 1. Identify the column names (based on previous analysis)\n    # The first column is the ID, the second is the prediction.\n    id_col = df.columns[0]\n    pred_col = df.columns[1]\n    \n    # 2. Get the prediction for the first (and only) image\n    image_id = df.loc[0, id_col]\n    prediction = df.loc[0, pred_col]\n\n    # 3. Determine authenticity\n    # A numeric RLE string means 'Forged'. The word 'authentic' means 'Authentic'.\n    if prediction.strip().lower() == 'authentic':\n        result = \"AUTHENTIC\"\n    elif prediction.replace(' ', '').replace('.', '').isdigit():\n        # Check if the string is composed purely of numbers and spaces (RLE format)\n        result = \"FORGED\"\n    else:\n        result = \"UNCERTAIN/INVALID FORMAT\"\n        \n    print(\"=\" * 40)\n    print(f\"ANALYSIS OF TEST IMAGE {image_id}:\")\n    print(\"-\" * 40)\n    print(f\"Model Prediction: {result}\")\n    print(f\"Prediction Value: {prediction[:30]}...\")\n    print(f\"Conclusion: Image {image_id}.png is predicted to be {result}.\")\n    print(\"=\" * 40)\n\n# Execute the analysis\nanalyze_authenticity()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:42:51.446367Z","iopub.execute_input":"2025-11-02T21:42:51.446924Z","iopub.status.idle":"2025-11-02T21:42:51.456913Z","shell.execute_reply.started":"2025-11-02T21:42:51.446902Z","shell.execute_reply":"2025-11-02T21:42:51.456212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# Analyze the file in the working directory\ntry:\n    df = pd.read_csv(\"submission.csv\")\n    print(\"\\nSUCCESS: File Loaded.\")\n    print(\"--- First 5 Rows ---\")\n    print(df.head())\n    print(\"\\n--- Summary Stats ---\")\n    print(df['predicted'].value_counts(normalize=True).head())\n    print(f\"\\nTotal rows: {len(df)}\")\nexcept FileNotFoundError:\n    print(\"FATAL: The 'submission.csv' file was not saved or is not accessible.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:44:00.791586Z","iopub.execute_input":"2025-11-02T21:44:00.792367Z","iopub.status.idle":"2025-11-02T21:44:00.801566Z","shell.execute_reply.started":"2025-11-02T21:44:00.792332Z","shell.execute_reply":"2025-11-02T21:44:00.800990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cat /kaggle/working/submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-02T21:44:12.521199Z","iopub.execute_input":"2025-11-02T21:44:12.521924Z","iopub.status.idle":"2025-11-02T21:44:12.683294Z","shell.execute_reply.started":"2025-11-02T21:44:12.521902Z","shell.execute_reply":"2025-11-02T21:44:12.682543Z"}},"outputs":[],"execution_count":null}]}