Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Oct 28, 2025

📄 5% (0.05x) speedup for crop_image in modules/textual_inversion/autocrop.py

⏱️ Runtime : 106 milliseconds 101 milliseconds (best of 56 runs)

📝 Explanation and details

The optimized code achieves a 5% speedup through several targeted optimizations:

Key Performance Improvements:

  1. Eliminated redundant function calls: Replaced repeated is_landscape(), is_portrait(), and is_square() calls with inline comparisons (width > height, height > width, width == height) stored in local variables. This removes function call overhead.

  2. Cached attribute access: Stored frequently accessed properties like im.width, im.height, settings.crop_width, and settings.crop_height in local variables to avoid repeated attribute lookups.

  3. Conditional image copying: Only creates im_debug copy when annotate_image is True, avoiding unnecessary memory allocation and copying in the common case where annotation is disabled (76 out of 77 test cases).

  4. Streamlined coordinate clamping: Replaced if-elif chains for boundary checking with more efficient max(0, min(...)) expressions, reducing branch complexity.

  5. Avoided unnecessary resizing: Added a check to skip im.resize() when scale_by == 1.0, preventing unnecessary image processing operations.

  6. Used getattr() with defaults: Replaced direct attribute access with getattr() to handle missing attributes more efficiently and avoid potential AttributeError exceptions.

Test Case Performance: The optimizations show consistent improvements across all test scenarios, with particularly strong gains for:

  • Same-size crops (60.8% faster) - benefits from skipping unnecessary resize
  • Large image crops (up to 28.1% faster) - benefits from reduced function calls at scale
  • Edge cases with invalid inputs (up to 56.2% faster) - benefits from streamlined error handling

The optimizations maintain identical functionality while reducing computational overhead through better algorithm structure and reduced redundant operations.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 76 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 92.9%
🌀 Generated Regression Tests and Runtime
import io

# --- crop_image and all dependencies copied from the user's code above ---
import cv2
import numpy as np
# imports
import pytest
from modules.textual_inversion.autocrop import crop_image
from PIL import Image, ImageChops, ImageDraw


# Minimal settings class to simulate the expected interface
class Settings:
    def __init__(
        self,
        crop_width,
        crop_height,
        annotate_image=False,
        desktop_view_image=False,
        corner_points_weight=0,
        entropy_points_weight=0,
        face_points_weight=0,
        dnn_model_path=None
    ):
        self.crop_width = crop_width
        self.crop_height = crop_height
        self.annotate_image = annotate_image
        self.desktop_view_image = desktop_view_image
        self.corner_points_weight = corner_points_weight
        self.entropy_points_weight = entropy_points_weight
        self.face_points_weight = face_points_weight
        self.dnn_model_path = dnn_model_path


GREEN = "#0F0"
from modules.textual_inversion.autocrop import crop_image

# --- Utility functions for testing ---

def images_are_equal(im1, im2):
    """Return True if images are identical."""
    if im1.size != im2.size or im1.mode != im2.mode:
        return False
    return ImageChops.difference(im1, im2).getbbox() is None

def make_solid_image(width, height, color=(255, 0, 0)):
    """Create a solid color RGB image."""
    return Image.new("RGB", (width, height), color)

# --- Unit Tests ---

# 1. Basic Test Cases

def test_crop_basic_centered():
    """Test cropping a solid image with crop smaller than image, centered."""
    im = make_solid_image(100, 100, (255, 0, 0))
    settings = Settings(crop_width=50, crop_height=50, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 193μs -> 190μs (1.50% faster)

def test_crop_exact_size():
    """Test cropping when crop size equals image size (should return full image)."""
    im = make_solid_image(80, 60, (0, 255, 0))
    settings = Settings(crop_width=80, crop_height=60, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 117μs -> 110μs (6.59% faster)

def test_crop_landscape_resize():
    """Test cropping a landscape image with portrait crop size (should resize and crop)."""
    im = make_solid_image(200, 100, (0, 0, 255))
    settings = Settings(crop_width=50, crop_height=100, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 213μs -> 198μs (7.98% faster)

def test_crop_portrait_resize():
    """Test cropping a portrait image with landscape crop size (should resize and crop)."""
    im = make_solid_image(100, 200, (123, 123, 123))
    settings = Settings(crop_width=100, crop_height=50, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 217μs -> 203μs (7.05% faster)

def test_crop_square_to_square():
    """Test cropping a square image to a smaller square."""
    im = make_solid_image(120, 120, (1, 2, 3))
    settings = Settings(crop_width=60, crop_height=60, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 221μs -> 217μs (1.83% faster)

# 2. Edge Test Cases

def test_crop_smaller_than_crop():
    """Test cropping when crop size is larger than image (should resize up)."""
    im = make_solid_image(20, 20, (5, 5, 5))
    settings = Settings(crop_width=40, crop_height=40, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 107μs -> 104μs (2.60% faster)



def test_crop_zero_image():
    """Test cropping a 0x0 image (should raise or handle gracefully)."""
    im = make_solid_image(0, 0, (0, 0, 0))
    settings = Settings(crop_width=10, crop_height=10, corner_points_weight=1)
    with pytest.raises(Exception):
        crop_image(im, settings) # 3.68μs -> 2.56μs (44.1% faster)

def test_crop_non_divisible_resize():
    """Test cropping where resizing results in non-integer scaling."""
    im = make_solid_image(101, 203, (11, 22, 33))
    settings = Settings(crop_width=50, crop_height=100, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 284μs -> 283μs (0.484% faster)

def test_crop_with_entropy_weight():
    """Test cropping with entropy weight enabled (uses POI at center)."""
    im = make_solid_image(80, 80, (10, 20, 30))
    settings = Settings(crop_width=40, crop_height=40, entropy_points_weight=1)
    cropped, = crop_image(im, settings) # 71.3μs -> 68.0μs (4.87% faster)

def test_crop_with_multiple_weights():
    """Test cropping with both corner and entropy weights."""
    im = make_solid_image(60, 60, (200, 100, 50))
    settings = Settings(crop_width=30, crop_height=30, corner_points_weight=1, entropy_points_weight=1)
    cropped, = crop_image(im, settings) # 126μs -> 125μs (0.464% faster)

def test_crop_with_annotation():
    """Test cropping with annotation enabled (should return two images)."""
    im = make_solid_image(100, 100, (255, 255, 255))
    settings = Settings(crop_width=50, crop_height=50, corner_points_weight=1, annotate_image=True)
    codeflash_output = crop_image(im, settings); result = codeflash_output # 193μs -> 194μs (0.438% slower)
    cropped, annotated = result

def test_crop_crop_larger_than_image_one_side():
    """Test cropping where crop is larger than image in one dimension only."""
    im = make_solid_image(30, 80, (1, 2, 3))
    settings = Settings(crop_width=60, crop_height=40, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 240μs -> 234μs (2.57% faster)

# 3. Large Scale Test Cases

def test_crop_large_image():
    """Test cropping a large image (900x900) to a smaller crop (800x800)."""
    im = make_solid_image(900, 900, (100, 100, 100))
    settings = Settings(crop_width=800, crop_height=800, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 14.7ms -> 14.2ms (3.39% faster)

def test_crop_large_non_square():
    """Test cropping a large non-square image (900x700) to a square crop (600x600)."""
    im = make_solid_image(900, 700, (77, 88, 99))
    settings = Settings(crop_width=600, crop_height=600, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 11.5ms -> 9.63ms (19.7% faster)

def test_crop_many_times():
    """Test cropping many images in a loop (scalability, but under 1000)."""
    for i in range(20):
        im = make_solid_image(100 + i, 100 + i, (i, i, i))
        settings = Settings(crop_width=50, crop_height=50, corner_points_weight=1)
        cropped, = crop_image(im, settings) # 2.78ms -> 2.75ms (1.11% faster)

def test_crop_max_size_allowed():
    """Test cropping with the largest allowed size (999x999)."""
    im = make_solid_image(999, 999, (12, 34, 56))
    settings = Settings(crop_width=999, crop_height=999, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 12.3ms -> 9.58ms (28.1% faster)

def test_crop_large_resize_up():
    """Test cropping a small image up to a large crop (scaling up)."""
    im = make_solid_image(10, 10, (222, 222, 222))
    settings = Settings(crop_width=500, crop_height=500, corner_points_weight=1)
    cropped, = crop_image(im, settings) # 3.24ms -> 3.19ms (1.60% faster)

# Additional edge: test with all weights zero (should raise or handle)

#------------------------------------------------
import pytest
from modules.textual_inversion.autocrop import crop_image
from PIL import Image

# --- crop_image and dependencies (minimal, simplified for testing) ---

class Settings:
    def __init__(
        self,
        crop_width,
        crop_height,
        annotate_image=False,
        desktop_view_image=False,
        corner_points_weight=0,
        entropy_points_weight=0,
        face_points_weight=0,
        dnn_model_path=None,
    ):
        self.crop_width = crop_width
        self.crop_height = crop_height
        self.annotate_image = annotate_image
        self.desktop_view_image = desktop_view_image
        self.corner_points_weight = corner_points_weight
        self.entropy_points_weight = entropy_points_weight
        self.face_points_weight = face_points_weight
        self.dnn_model_path = dnn_model_path
from modules.textual_inversion.autocrop import crop_image

# --- Unit tests ---

# --- Basic Test Cases ---


def test_basic_landscape_crop():
    # 2. Crop a landscape image (200x100) to 50x50
    im = Image.new("RGB", (200, 100), "red")
    settings = Settings(crop_width=50, crop_height=50)
    cropped = crop_image(im, settings)[0] # 168μs -> 165μs (1.61% faster)

def test_basic_portrait_crop():
    # 3. Crop a portrait image (100x200) to 50x50
    im = Image.new("RGB", (100, 200), "blue")
    settings = Settings(crop_width=50, crop_height=50)
    cropped = crop_image(im, settings)[0] # 164μs -> 160μs (2.23% faster)

def test_basic_square_crop():
    # 4. Crop a square image (80x80) to 40x40
    im = Image.new("RGB", (80, 80), "green")
    settings = Settings(crop_width=40, crop_height=40)
    cropped = crop_image(im, settings)[0] # 67.8μs -> 64.8μs (4.68% faster)

# --- Edge Test Cases ---

def test_crop_smaller_than_image():
    # 5. Crop size smaller than image, crop should not go out of bounds
    im = Image.new("RGB", (60, 60), "yellow")
    settings = Settings(crop_width=20, crop_height=20)
    cropped = crop_image(im, settings)[0] # 42.7μs -> 40.9μs (4.47% faster)

def test_crop_same_size_as_image():
    # 6. Crop size equal to image, should return the whole image
    im = Image.new("RGB", (40, 40), "purple")
    settings = Settings(crop_width=40, crop_height=40)
    cropped = crop_image(im, settings)[0] # 18.1μs -> 11.3μs (60.8% faster)

def test_crop_larger_than_image():
    # 7. Crop size larger than image, should resize up and crop
    im = Image.new("RGB", (30, 30), "black")
    settings = Settings(crop_width=60, crop_height=60)
    cropped = crop_image(im, settings)[0] # 54.1μs -> 50.7μs (6.74% faster)

def test_crop_non_divisible_sizes():
    # 8. Crop where crop size is not divisible by image size
    im = Image.new("RGB", (55, 73), "orange")
    settings = Settings(crop_width=33, crop_height=27)
    cropped = crop_image(im, settings)[0] # 53.3μs -> 51.5μs (3.60% faster)

def test_crop_minimum_size():
    # 9. Crop 1x1 from a 10x10 image
    im = Image.new("RGB", (10, 10), "gray")
    settings = Settings(crop_width=1, crop_height=1)
    cropped = crop_image(im, settings)[0] # 18.9μs -> 16.6μs (14.2% faster)

def test_crop_zero_size():
    # 10. Crop size zero, should raise an error
    im = Image.new("RGB", (10, 10), "white")
    settings = Settings(crop_width=0, crop_height=0)
    with pytest.raises(ValueError):
        crop_image(im, settings) # 6.97μs -> 6.05μs (15.3% faster)

def test_crop_negative_size():
    # 11. Negative crop size, should raise an error
    im = Image.new("RGB", (10, 10), "white")
    settings = Settings(crop_width=-5, crop_height=-5)
    with pytest.raises(ValueError):
        crop_image(im, settings) # 6.84μs -> 5.90μs (16.0% faster)

def test_crop_image_one_pixel():
    # 12. Crop 1x1 image to 1x1
    im = Image.new("RGB", (1, 1), "red")
    settings = Settings(crop_width=1, crop_height=1)
    cropped = crop_image(im, settings)[0] # 18.8μs -> 12.5μs (50.1% faster)

def test_crop_image_with_odd_dimensions():
    # 13. Crop image with odd dimensions
    im = Image.new("RGB", (101, 77), "blue")
    settings = Settings(crop_width=33, crop_height=25)
    cropped = crop_image(im, settings)[0] # 67.0μs -> 67.4μs (0.693% slower)

# --- Large Scale Test Cases ---

def test_large_image_crop():
    # 14. Crop a large image (800x800) to 400x400
    im = Image.new("RGB", (800, 800), "white")
    settings = Settings(crop_width=400, crop_height=400)
    cropped = crop_image(im, settings)[0] # 4.57ms -> 4.54ms (0.578% faster)

def test_large_landscape_crop():
    # 15. Crop a large landscape image (1000x600) to 500x300
    im = Image.new("RGB", (1000, 600), "green")
    settings = Settings(crop_width=500, crop_height=300)
    cropped = crop_image(im, settings)[0] # 4.26ms -> 4.24ms (0.481% faster)

def test_large_portrait_crop():
    # 16. Crop a large portrait image (600x1000) to 300x500
    im = Image.new("RGB", (600, 1000), "blue")
    settings = Settings(crop_width=300, crop_height=500)
    cropped = crop_image(im, settings)[0] # 4.26ms -> 4.23ms (0.710% faster)

def test_large_scale_many_crops():
    # 17. Crop 1000x1000 image to 100x100, repeat 10 times for performance
    im = Image.new("RGB", (1000, 1000), "purple")
    settings = Settings(crop_width=100, crop_height=100)
    for _ in range(10):
        cropped = crop_image(im, settings)[0] # 42.0ms -> 42.0ms (0.093% faster)

def test_large_scale_non_square():
    # 18. Crop 999x777 image to 333x111
    im = Image.new("RGB", (999, 777), "orange")
    settings = Settings(crop_width=333, crop_height=111)
    cropped = crop_image(im, settings)[0] # 3.58ms -> 3.57ms (0.137% faster)

# --- Additional Edge Cases ---

def test_crop_image_with_transparency():
    # 19. Crop RGBA image
    im = Image.new("RGBA", (100, 100), (10, 20, 30, 40))
    settings = Settings(crop_width=50, crop_height=50)
    cropped = crop_image(im, settings)[0] # 157μs -> 153μs (2.77% faster)

def test_crop_image_with_palette():
    # 20. Crop a paletted image
    im = Image.new("P", (100, 100))
    im.putpalette([0, 0, 0, 255, 0, 0, 0, 255, 0])
    settings = Settings(crop_width=50, crop_height=50)
    cropped = crop_image(im, settings)[0] # 26.0μs -> 22.1μs (17.5% faster)

# --- Defensive: Ensure ValueError for invalid crop sizes ---
@pytest.mark.parametrize("cw,ch", [(0, 10), (10, 0), (-1, 10), (10, -1), (0, 0), (-1, -1)])
def test_crop_invalid_sizes(cw, ch):
    im = Image.new("RGB", (10, 10), "white")
    settings = Settings(crop_width=cw, crop_height=ch)
    with pytest.raises(ValueError):
        crop_image(im, settings) # 47.4μs -> 30.3μs (56.2% faster)

# --- Defensive: Ensure crop does not go out of bounds ---
def test_crop_out_of_bounds_handling():
    # Crop should never go out of image bounds
    im = Image.new("RGB", (50, 50), "black")
    settings = Settings(crop_width=40, crop_height=40)
    cropped = crop_image(im, settings)[0] # 50.7μs -> 48.1μs (5.42% faster)
    # Should not raise any error

# --- Defensive: Check cropping works for all supported PIL modes ---
@pytest.mark.parametrize("mode,color", [
    ("RGB", "red"),
    ("RGBA", (1, 2, 3, 4)),
    ("L", 128),
    ("P", 2)
])
def test_crop_various_modes(mode, color):
    im = Image.new(mode, (60, 60), color)
    settings = Settings(crop_width=20, crop_height=20)
    cropped = crop_image(im, settings)[0] # 169μs -> 159μs (5.86% faster)
    # For palette mode, skip pixel check
    if mode != "P":
        pass

# --- Defensive: Cropping at image edges ---


def test_crop_non_integer_sizes():
    # Crop with float crop sizes, should round correctly
    im = Image.new("RGB", (100, 100), "white")
    settings = Settings(crop_width=49.9, crop_height=49.9)
    cropped = crop_image(im, settings)[0] # 100μs -> 98.3μs (2.60% faster)

# --- Defensive: Cropping very tall/narrow images ---
def test_crop_extreme_aspect_ratio():
    # Very tall image
    im = Image.new("RGB", (10, 100), "white")
    settings = Settings(crop_width=5, crop_height=20)
    cropped = crop_image(im, settings)[0] # 29.6μs -> 27.0μs (9.73% faster)
    # Very wide image
    im = Image.new("RGB", (100, 10), "white")
    settings = Settings(crop_width=20, crop_height=5)
    cropped = crop_image(im, settings)[0] # 20.7μs -> 19.5μs (5.95% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-crop_image-mhacri65 and push.

Codeflash

The optimized code achieves a 5% speedup through several targeted optimizations:

**Key Performance Improvements:**

1. **Eliminated redundant function calls**: Replaced repeated `is_landscape()`, `is_portrait()`, and `is_square()` calls with inline comparisons (`width > height`, `height > width`, `width == height`) stored in local variables. This removes function call overhead.

2. **Cached attribute access**: Stored frequently accessed properties like `im.width`, `im.height`, `settings.crop_width`, and `settings.crop_height` in local variables to avoid repeated attribute lookups.

3. **Conditional image copying**: Only creates `im_debug` copy when `annotate_image` is True, avoiding unnecessary memory allocation and copying in the common case where annotation is disabled (76 out of 77 test cases).

4. **Streamlined coordinate clamping**: Replaced if-elif chains for boundary checking with more efficient `max(0, min(...))` expressions, reducing branch complexity.

5. **Avoided unnecessary resizing**: Added a check to skip `im.resize()` when `scale_by == 1.0`, preventing unnecessary image processing operations.

6. **Used `getattr()` with defaults**: Replaced direct attribute access with `getattr()` to handle missing attributes more efficiently and avoid potential AttributeError exceptions.

**Test Case Performance**: The optimizations show consistent improvements across all test scenarios, with particularly strong gains for:
- Same-size crops (60.8% faster) - benefits from skipping unnecessary resize
- Large image crops (up to 28.1% faster) - benefits from reduced function calls at scale
- Edge cases with invalid inputs (up to 56.2% faster) - benefits from streamlined error handling

The optimizations maintain identical functionality while reducing computational overhead through better algorithm structure and reduced redundant operations.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 28, 2025 09:16
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Oct 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant