Interactive Image Cropping in Python

Motivation

For many Pythonic image analysis applications, there is a need for a rapid image cropping utility. Although existing Python packages like pillow and scikit-image permit users to programmatically crop images, these tools do not allow interactive adjustment of the crop size. Similarly, many external image processing tools, such as MS Paint, Preview on Mac, and the Adobe Photoshop Suite allow users to crop images via graphical procedures; However, in these programs it is often tedious to pipe the crop sizes back to Python while retaining the original image, and similarly time-consuming to rapidly crop a group of images. For these applications, a valid solution would integrate the cropping as a configurable component of the program data flow, adjacent to the source rather than in a separate window or program.

Solution

We recently developed a tool to perform these rapid cropping functions within the Jupyter Lab/Notebooks framework. The cropping tool integrates functionality from the interactive ipywidgets package, the image modification library pillow, and the foundational ndarrays from the numpy package. The solution, as displayed in the below video, permits users to crop images to arbitrary size through two sliders to select range and a dropdown image selector. The tool functionality is shown in the below video.

Installation

The cropping widget is accessible in the PyPI repository via pip install interactivecrop. This will install the package interactivecrop, where the cropping widget can be obtained via the command: from interactivecrop.interactivecrop import main. Similarly, lists of sample images and image names can be obtained via: from interactivecrop.samples import sample_images, sample_names.

Cropping Tool Configuration

Function Arguments

image_list

This is a list of either local paths to images or images as np.ndarray objects. The example set provided in interactivecrop.samples is a list of images as np.ndarray objects.

image_name_list (optional, default: [])

A list with the same length as the image_list, describing the names of the images.

continuous_update (optional, default: True)

A boolean indicating if the program should update dynamically as the slider is dragged, or only when the slider is released.

crop_shape (optional, default: ‘Rectangle’)

The desired crop shape, as defined in the interactivecrop.shapes. Options are “Rectangle”, “Ellipse”, or “Triangle”.

optimize (optional, default: True)

The program provides a boolean argument to indicate if the images can be resized and converted to grayscale for faster rendering. If True (default), all images will be converted to grayscale and only a resized, smaller version of the image will be displayed (up to a maximum size of 200 KiloPixels). If False, the program will present the full color image, but will typically have slower rendering times. See the example below considering kwargs crop_shape='Ellipse' and optimize=False.

noOptimizeandEllipse

callback (optional, default: prints cropped size)

If a callback function is provided, the function is called when the Save Crop Sizes button is pressed, and is provided the image name and the shape object defined in interactivecrop.shapes (either Rectangle, Triangle, or Ellipse). This button can be used to add the crop sizes to a .csv sheet, append to a text file, print out, or any desired operation. For the scientific image analysis in our research lab, we use this callback to save the crop size to a local .hdf5 file for the test, and then save the cropped image into the respective test folder; In this way, the cropped data is persistent in the event of program failure or other error. For an example of the callback function use, see the below example:

# Define the callback to save the image
def callback(image_name, shape):
    shape.draw() # Draw the shape onto the PIL image; Otherwise we print the unmodified image. 
    # Note draw() is inplace method
    PIL_im = shape.image # Access the PIL image from the 'shape' object
    d = ImageDraw.Draw(PIL_im)
    d.text((10,10), text=str(shape.size), fill='white') # Draw the crop shape onto the image
    PIL_im.save(image_name+'_w_Text.jpg') # Saves the image with text
    
crop(image_list, image_name_list=image_name_list, callback=callback, crop_shape = 'Ellipse')

Lab or Notebook Frameworks

The tool supports both Jupyter Lab interface, as shown in previous examples, and the Notebook interface, as shown below. Feel free to use the program of your choice!

notebook

Further Info and Contributions

If you would like to see some further examples of the cropping tool configuration options, please see the Examples notebook on our Github page. For further information or to download the source, please see our Github page. If you find an error in the program, feel free to report it in the Issues section on Github. We’re also open for contributions if you are interested in adding functionality or fixing bugs via a pull request. Thanks for reading, and we hope you get some use out of this tool!

comments powered by Disqus