{"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":49349,"databundleVersionId":5447706,"isSourceIdPinned":false,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Comparison of Image Normalization Methods**","metadata":{}},{"cell_type":"markdown","source":"\n\n---\n\n\n### 1. Simple Center Crop (`normalize_image_sizes`)\n\nThis is the standard approach for most deep learning models. It extracts a single square from the middle of the image.\n\n* **Logic:** Finds the shorter side, crops the center of the image to that size, and then resizes to the target.\n* **Pros:** Keeps the most important part of the image (usually the center).\n* **Cons:** Information at the edges (the \"left\" and \"right\" or \"top\" and \"bottom\") is permanently lost.\n* **Best for:** General classification tasks where the object of interest is centered.\n\n### 2. Triplet Cropping (`normalize_image_sizes_triplet`)\n\nThis method acts as a form of **Data Augmentation**. It turns one original image into three distinct samples.\n\n* **Logic:** Creates three separate square crops:\n* **Landscape:** Left, Center, and Right.\n* **Portrait:** Top, Center, and Bottom.\n\n\n* **Pros:** Triples your dataset size and ensures the model sees features located at the edges.\n* **Cons:** Increases storage requirements and training time.\n* **Best for:** Tasks like the \"Image Matching Challenge\" where features might be anywhere in the frame, or when you have a small dataset.\n\n### 3. Aspect Ratio Modes (`normalize_image_sizes2`)\n\nThis method offers flexibility depending on whether you prioritize the image content or a consistent square shape.\n\n| Mode | Visual Result | Description |\n| --- | --- | --- |\n| **Fit** | Rectangular | Resizes the **longer side** to the target. The image stays a rectangle. No data is lost, but the output size is not a consistent square. |\n| **Fill** | Rectangular | Resizes the **shorter side** to the target. Often used before a crop. No data is lost, but the image will be larger than the target size. |\n| **Pad** | Square | Resizes the image to fit inside the target and adds **black bars (letterboxing)** to fill the gaps. |\n\n* **Pros:** `Pad` preserves the 100% original composition without cropping anything.\n* **Cons:** `Pad` introduces black pixels which the model might find distracting; `Fit/Fill` result in inconsistent dimensions.\n* **Best for:** `Pad` is excellent for object detection where preserving the full context is more important than filling the whole square.\n\n---\n\n### Summary Table\n\n| Method | Resulting Images | Shape | Preserves All Data? | Use Case |\n| --- | --- | --- | --- | --- |\n| **Center Crop** | 1 | Square | No | Standard Classification |\n| **Triplet** | 3 | Square | Yes (Across 3 images) | Data Augmentation / Matching |\n| **Fit / Fill** | 1 | Rectangle | Yes | Custom Neural Networks |\n| **Pad** | 1 | Square | Yes | Detection / Composition-sensitive |\n\nWould you like me to help you write a script to visualize these different outputs side-by-side using a single test image?","metadata":{}},{"cell_type":"code","source":"import os\nfrom PIL import Image\n\ninput_dir='/kaggle/input/image-matching-challenge-2023/train/phototourism/grand_place_brussels/images'\noutput_dir='/kaggle/working/output'\n!mkdir {output_dir}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:01.874083Z","iopub.execute_input":"2026-01-08T02:31:01.874425Z","iopub.status.idle":"2026-01-08T02:31:01.999030Z","shell.execute_reply.started":"2026-01-08T02:31:01.874402Z","shell.execute_reply":"2026-01-08T02:31:01.997625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_image_sizes(input_dir, output_dir=None, target_size=400):\n\n    os.makedirs(output_dir, exist_ok=True)\n\n    print(f\"Normalizing image sizes to {target_size}x{target_size}...\")\n    print()\n\n    size_stats = {}\n    converted_count = 0\n\n    for img_file in sorted(os.listdir(input_dir))[0:1]:\n        if not img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            continue\n\n        input_path = os.path.join(input_dir, img_file)\n        output_path = os.path.join(output_dir, img_file)\n\n        try:\n            img = Image.open(input_path)\n            original_size = img.size  # (width, height)\n            display(img)\n\n            size_key = f\"{original_size[0]}x{original_size[1]}\"\n            if size_key not in size_stats:\n                size_stats[size_key] = 0\n            size_stats[size_key] += 1\n\n            img = center_crop_and_resize(img, target_size)\n            img.save(output_path, quality=95)\n            converted_count += 1\n            display(img)\n\n            print(f\"  ✓ {img_file}: {original_size} → {target_size}x{target_size}\")\n\n        except Exception as e:\n            print(f\"  ✗ Error processing {img_file}: {e}\")\n\n    print(f\"\\nConversion complete: {converted_count} images\")\n    print(f\"Original size distribution: {size_stats}\")\n    return converted_count\n    \n\ndef center_crop_and_resize(img, target_size):\n\n    width, height = img.size\n\n    crop_size = min(width, height)\n    left = (width - crop_size) // 2\n    top = (height - crop_size) // 2\n    right = left + crop_size\n    bottom = top + crop_size\n\n    img_cropped = img.crop((left, top, right, bottom))\n    img_resized = img_cropped.resize((target_size, target_size), Image.Resampling.LANCZOS)\n\n    return img_resized","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.001650Z","iopub.execute_input":"2026-01-08T02:31:02.002428Z","iopub.status.idle":"2026-01-08T02:31:02.013630Z","shell.execute_reply.started":"2026-01-08T02:31:02.002394Z","shell.execute_reply":"2026-01-08T02:31:02.012055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes(input_dir,output_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.014896Z","iopub.execute_input":"2026-01-08T02:31:02.015210Z","iopub.status.idle":"2026-01-08T02:31:02.243745Z","shell.execute_reply.started":"2026-01-08T02:31:02.015177Z","shell.execute_reply":"2026-01-08T02:31:02.242639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_image_sizes_triplet(input_dir, output_dir=None, target_size=400):\n    \"\"\"\n    Generates three square crops (Left/Top, Center, Right/Bottom) \n    from each image in a directory.\n    \"\"\"\n    if output_dir is None:\n        output_dir = input_dir\n\n    os.makedirs(output_dir, exist_ok=True)\n\n    print(f\"Generating 3 cropped squares (Left/Top, Center, Right/Bottom) for each image...\")\n    print()\n\n    converted_count = 0\n    size_stats = {}\n\n    # Processing images (currently using [0:1] for testing)\n    for img_file in sorted(os.listdir(input_dir))[0:1]:\n        if not img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            continue\n\n        input_path = os.path.join(input_dir, img_file)\n        \n        try:\n            img = Image.open(input_path)\n            original_size = img.size\n            \n            # Record original size for statistics\n            size_key = f\"{original_size[0]}x{original_size[1]}\"\n            size_stats[size_key] = size_stats.get(size_key, 0) + 1\n\n            # Generate the 3 crop patterns\n            crops = generate_three_crops(img, target_size)\n            \n            # Save each crop pattern\n            base_name, ext = os.path.splitext(img_file)\n            for mode, cropped_img in crops.items():\n                output_path = os.path.join(output_dir, f\"{base_name}_{mode}{ext}\")\n                cropped_img.save(output_path, quality=95)\n                display(cropped_img)\n            \n            converted_count += 1\n            print(f\"  ✓ {img_file}: {original_size} → 3 square images generated\")\n\n        except Exception as e:\n            print(f\"  ✗ Error processing {img_file}: {e}\")\n\n    print(f\"\\nProcessing complete: {converted_count} source images processed\")\n    print(f\"Original size distribution: {size_stats}\")\n    return converted_count\n\n\ndef generate_three_crops(img, target_size):\n    \"\"\"\n    Crops the image into a square and returns 3 variations (Left/Center/Right or Top/Center/Bottom).\n    \"\"\"\n    width, height = img.size\n    crop_size = min(width, height)\n    crops = {}\n\n    if width > height:\n        # For Landscape: Left, Center, Right\n        positions = {\n            'left': 0,\n            'center': (width - crop_size) // 2,\n            'right': width - crop_size\n        }\n        for mode, x_offset in positions.items():\n            box = (x_offset, 0, x_offset + crop_size, crop_size)\n            crops[mode] = img.crop(box).resize((target_size, target_size), Image.Resampling.LANCZOS)\n    \n    else:\n        # For Portrait or Square: Top, Center, Bottom\n        positions = {\n            'top': 0,\n            'center': (height - crop_size) // 2,\n            'bottom': height - crop_size\n        }\n        for mode, y_offset in positions.items():\n            box = (0, y_offset, crop_size, y_offset + crop_size)\n            crops[mode] = img.crop(box).resize((target_size, target_size), Image.Resampling.LANCZOS)\n\n    return crops","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.245516Z","iopub.execute_input":"2026-01-08T02:31:02.246371Z","iopub.status.idle":"2026-01-08T02:31:02.258009Z","shell.execute_reply.started":"2026-01-08T02:31:02.246338Z","shell.execute_reply":"2026-01-08T02:31:02.256905Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes_triplet(input_dir, output_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.258991Z","iopub.execute_input":"2026-01-08T02:31:02.259420Z","iopub.status.idle":"2026-01-08T02:31:02.426154Z","shell.execute_reply.started":"2026-01-08T02:31:02.259391Z","shell.execute_reply":"2026-01-08T02:31:02.425340Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_image_sizes2(input_dir, output_dir=None, target_size=400, mode='fit'):\n    \"\"\"\n    Resizes all images in a directory while maintaining aspect ratio.\n\n    Args:\n        input_dir: Directory containing input images.\n        output_dir: Directory to save the processed images. Defaults to input_dir.\n        target_size: The desired maximum size for the longer side (or minimum size for the shorter side).\n        mode: Resizing mode - 'fit' (fit within target), 'fill' (fill target), or 'pad' (fit with padding).\n    \"\"\"\n    if output_dir is None:\n        output_dir = input_dir\n\n    os.makedirs(output_dir, exist_ok=True)\n\n    print(f\"Normalizing image sizes (mode: {mode}) while maintaining aspect ratio...\")\n\n    size_stats = {}\n    converted_count = 0\n\n    # Note: currently limited to the first image for testing/sample purposes\n    for img_file in sorted(os.listdir(input_dir))[0:1]:\n        if not img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            continue\n\n        input_path = os.path.join(input_dir, img_file)\n        output_path = os.path.join(output_dir, img_file)\n\n        try:\n            img = Image.open(input_path)\n            original_size = img.size  # (width, height)\n            original_aspect = original_size[0] / original_size[1]\n\n            # Record original size for statistics\n            size_key = f\"{original_size[0]}x{original_size[1]}\"\n            if size_key not in size_stats:\n                size_stats[size_key] = 0\n            size_stats[size_key] += 1\n\n            # Resize while maintaining aspect ratio\n            if mode == 'fit':\n                # Fit within target (Resize the longer side to target_size)\n                if original_size[0] > original_size[1]:  # Landscape\n                    new_width = target_size\n                    new_height = int(target_size / original_aspect)\n                else:  # Portrait or Square\n                    new_height = target_size\n                    new_width = int(target_size * original_aspect)\n\n            elif mode == 'fill':\n                # Fill target (Resize the shorter side to target_size)\n                if original_size[0] > original_size[1]:  # Landscape\n                    new_height = target_size\n                    new_width = int(target_size * original_aspect)\n                else:  # Portrait or Square\n                    new_width = target_size\n                    new_height = int(target_size / original_aspect)\n\n            elif mode == 'pad':\n                # Fit with padding (Resize the longer side to target_size, then add padding)\n                if original_size[0] > original_size[1]:  # Landscape\n                    new_width = target_size\n                    new_height = int(target_size / original_aspect)\n                else:  # Portrait or Square\n                    new_height = target_size\n                    new_width = int(target_size * original_aspect)\n\n                # Add padding to create a square image\n                img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)\n                img_square = Image.new('RGB', (target_size, target_size), (0,0,0)) #(255,255,255)\n                offset = ((target_size - new_width) // 2, (target_size - new_height) // 2)\n                img_square.paste(img_resized, offset)\n                img = img_square\n                \n                print(f\"  ✓ {img_file}: {original_size} → {new_width}x{new_height} (padded to {target_size}x{target_size})\")\n                img.save(output_path, quality=95)\n                converted_count += 1\n                display(img)    \n                \n                continue\n\n            else:\n                raise ValueError(f\"Unknown mode: {mode}. Use 'fit', 'fill', or 'pad'.\")\n\n            # Execute resizing\n            img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)\n            img_resized.save(output_path, quality=95)\n            converted_count += 1\n            display(img_resized)\n\n            print(f\"  ✓ {img_file}: {original_size} → {new_width}x{new_height} (aspect ratio: {original_aspect:.2f})\")\n\n        except Exception as e:\n            print(f\"  ✗ Error processing {img_file}: {e}\")\n\n    print(f\"\\nConversion complete: {converted_count} images\")\n    print(f\"Original size distribution: {size_stats}\")\n    return converted_count\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.427346Z","iopub.execute_input":"2026-01-08T02:31:02.427933Z","iopub.status.idle":"2026-01-08T02:31:02.441441Z","shell.execute_reply.started":"2026-01-08T02:31:02.427911Z","shell.execute_reply":"2026-01-08T02:31:02.440218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes2(input_dir,output_dir,mode='fit')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.442320Z","iopub.execute_input":"2026-01-08T02:31:02.442656Z","iopub.status.idle":"2026-01-08T02:31:02.516958Z","shell.execute_reply.started":"2026-01-08T02:31:02.442633Z","shell.execute_reply":"2026-01-08T02:31:02.515710Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes2(input_dir,output_dir,mode='fill')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.518142Z","iopub.execute_input":"2026-01-08T02:31:02.518467Z","iopub.status.idle":"2026-01-08T02:31:02.608205Z","shell.execute_reply.started":"2026-01-08T02:31:02.518438Z","shell.execute_reply":"2026-01-08T02:31:02.607001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes2(input_dir,output_dir,mode='pad')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T02:31:02.609154Z","iopub.execute_input":"2026-01-08T02:31:02.609378Z","iopub.status.idle":"2026-01-08T02:31:02.666276Z","shell.execute_reply.started":"2026-01-08T02:31:02.609361Z","shell.execute_reply":"2026-01-08T02:31:02.665259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def normalize_image_sizes_biplet(input_dir, output_dir=None, target_size=400):\n    \"\"\"\n    Generates two square crops (Left & Right or Top & Bottom)\n    from each image in a directory.\n    \"\"\"\n    if output_dir is None:\n        output_dir = input_dir\n\n    os.makedirs(output_dir, exist_ok=True)\n\n    print(f\"Generating 2 cropped squares (Left/Right or Top/Bottom) for each image...\")\n    print()\n\n    converted_count = 0\n    size_stats = {}\n\n    for img_file in sorted(os.listdir(input_dir))[0:1]:\n        if not img_file.lower().endswith(('.jpg', '.jpeg', '.png')):\n            continue\n\n        input_path = os.path.join(input_dir, img_file)\n\n        try:\n            img = Image.open(input_path)\n            original_size = img.size\n\n            size_key = f\"{original_size[0]}x{original_size[1]}\"\n            size_stats[size_key] = size_stats.get(size_key, 0) + 1\n\n            # Generate 2 crops\n            crops = generate_two_crops(img, target_size)\n\n            base_name, ext = os.path.splitext(img_file)\n            for mode, cropped_img in crops.items():\n                output_path = os.path.join(output_dir, f\"{base_name}_{mode}{ext}\")\n                cropped_img.save(output_path, quality=95)\n                display(cropped_img)\n\n            converted_count += 1\n            print(f\"  ✓ {img_file}: {original_size} → 2 square images generated\")\n\n        except Exception as e:\n            print(f\"  ✗ Error processing {img_file}: {e}\")\n\n    print(f\"\\nProcessing complete: {converted_count} source images processed\")\n    print(f\"Original size distribution: {size_stats}\")\n    return converted_count\n\n\ndef generate_two_crops(img, target_size):\n    \"\"\"\n    Crops the image into a square and returns 2 variations\n    (Left/Right for landscape, Top/Bottom for portrait).\n    \"\"\"\n    width, height = img.size\n    crop_size = min(width, height)\n    crops = {}\n\n    if width > height:\n        # Landscape → Left & Right\n        positions = {\n            'left': 0,\n            'right': width - crop_size\n        }\n        for mode, x_offset in positions.items():\n            box = (x_offset, 0, x_offset + crop_size, crop_size)\n            crops[mode] = img.crop(box).resize(\n                (target_size, target_size),\n                Image.Resampling.LANCZOS\n            )\n\n    else:\n        # Portrait or Square → Top & Bottom\n        positions = {\n            'top': 0,\n            'bottom': height - crop_size\n        }\n        for mode, y_offset in positions.items():\n            box = (0, y_offset, crop_size, y_offset + crop_size)\n            crops[mode] = img.crop(box).resize(\n                (target_size, target_size),\n                Image.Resampling.LANCZOS\n            )\n\n    return crops\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"normalize_image_sizes_biplet(input_dir, output_dir)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}