Open Event Server: Testing Image Resize Using PIL and Unittest
FOSSASIA‘s Open Event Server project uses a certain set of functions in order to resize image from its original, example to thumbnail, icon or larger image. How do we test this resizing of images functions in Open Event Server project? To test image dimensions resizing functionality, we need to verify that the the resized image dimensions is same as the dimensions provided for resize. For example, in this function, we provide the url for the image that we received and it creates a resized image and saves the resized version.
def create_save_resized_image(image_file, basewidth, maintain_aspect, height_size, upload_path, ext='jpg', remove_after_upload=False, resize=True): """ Create and Save the resized version of the background image :param resize: :param upload_path: :param ext: :param remove_after_upload: :param height_size: :param maintain_aspect: :param basewidth: :param image_file: :return: """ filename = '{filename}.{ext}'.format(filename=get_file_name(), ext=ext) image_file = cStringIO.StringIO(urllib.urlopen(image_file).read()) im = Image.open(image_file) # Convert to jpeg for lower file size. if im.format is not 'JPEG': img = im.convert('RGB') else: img = im if resize: if maintain_aspect: width_percent = (basewidth / float(img.size[0])) height_size = int((float(img.size[1]) * float(width_percent))) img = img.resize((basewidth, height_size), PIL.Image.ANTIALIAS) temp_file_relative_path = 'static/media/temp/' + generate_hash(str(image_file)) + get_file_name() + '.jpg' temp_file_path = app.config['BASE_DIR'] + '/' + temp_file_relative_path dir_path = temp_file_path.rsplit('/', 1)[0] # create dirs if not present if not os.path.isdir(dir_path): os.makedirs(dir_path) img.save(temp_file_path) upfile = UploadedFile(file_path=temp_file_path, filename=filename) if remove_after_upload: os.remove(image_file) uploaded_url = upload(upfile, upload_path) os.remove(temp_file_path) return uploaded_url
In this function, we send the image url, the width and height to be resized to, and the aspect ratio as either True or False along with the folder to be saved. For this blog, we are gonna assume aspect ratio is False which means that we don’t maintain the aspect ratio while resizing. So, given the above mentioned as parameter, we get the url for the resized image that is saved.
To test whether it has been resized to correct dimensions, we use Pillow or as it is popularly know, PIL. So we write a separate function named getsizes() within which get the image file as a parameter. Then using the Image module of PIL, we open the file as a JpegImageFile object. The JpegImageFile object has an attribute size which returns (width, height). So from this function, we return the size attribute. Following is the code:
def getsizes(self, file): # get file size *and* image size (None if not known) im = Image.open(file) return im.size
As we have this function, it’s time to look into the unit testing function. So in unit testing we set dummy width and height that we want to resize to, set aspect ratio as false as discussed above. This helps us to test that both width and height are properly resized. We are using a creative commons licensed image for resizing. This is the code:
def test_create_save_resized_image(self): with app.test_request_context(): image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg' width = 500 height = 200 aspect_ratio = False upload_path = 'test' resized_image_url = create_save_resized_image(image_url_test, width, aspect_ratio, height, upload_path, ext='png') resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1] resized_width, resized_height = self.getsizes(resized_image_file)
In the above code from create_save_resized_image, we receive the url for the resized image. Since we have written all the unittests for local settings, we get a url with localhost as the server set. However, we don’t have the server running so we can’t acces the image through the url. So we build the absolute path to the image file from the url and store it in resized_image_file. Then we find the sizes of the image using the getsizes function that we have already written. This gives us the width and height of the newly resized image. We make an assertion now to check whether the width that we wanted to resize to is equal to the actual width of the resized image. We make the same check with height as well. If both match, then the resizing function had worked perfectly. Here is the complete code:
def test_create_save_resized_image(self): with app.test_request_context(): image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg' width = 500 height = 200 aspect_ratio = False upload_path = 'test' resized_image_url = create_save_resized_image(image_url_test, width, aspect_ratio, height, upload_path, ext='png') resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1] resized_width, resized_height = self.getsizes(resized_image_file) self.assertTrue(os.path.exists(resized_image_file)) self.assertEqual(resized_width, width) self.assertEqual(resized_height, height)
In open event orga server, we use this resize function to basically create 3 resized images in various modules, such as events, users,etc. The 3 sizes are names – Large, Thumbnail and Icon. Depending on the one more suitable we use it avoiding the need to load a very big image for a very small div. The exact width and height for these 3 sizes can be changed from the admin settings of the project. We use the same technique as mentioned above. We run a loop to check the sizes for all these. Here is the code:
def test_create_save_image_sizes(self): with app.test_request_context(): image_url_test = 'https://cdn.pixabay.com/photo/2014/09/08/17/08/hot-air-balloons-439331_960_720.jpg' image_sizes_type = "event" width_large = 1300 width_thumbnail = 500 width_icon = 75 image_sizes = create_save_image_sizes(image_url_test, image_sizes_type) resized_image_url = image_sizes['original_image_url'] resized_image_url_large = image_sizes['large_image_url'] resized_image_url_thumbnail = image_sizes['thumbnail_image_url'] resized_image_url_icon = image_sizes['icon_image_url'] resized_image_file = app.config.get('BASE_DIR') + resized_image_url.split('/localhost')[1] resized_image_file_large = app.config.get('BASE_DIR') + resized_image_url_large.split('/localhost')[1] resized_image_file_thumbnail = app.config.get('BASE_DIR') + resized_image_url_thumbnail.split('/localhost')[1] resized_image_file_icon = app.config.get('BASE_DIR') + resized_image_url_icon.split('/localhost')[1] resized_width_large, _ = self.getsizes(resized_image_file_large) resized_width_thumbnail, _ = self.getsizes(resized_image_file_thumbnail) resized_width_icon, _ = self.getsizes(resized_image_file_icon) self.assertTrue(os.path.exists(resized_image_file)) self.assertEqual(resized_width_large, width_large) self.assertEqual(resized_width_thumbnail, width_thumbnail) self.assertEqual(resized_width_icon, width_icon)
Resources:
- Learn about pillow function for image resize test: http://pillow.readthedocs.io/en/3.1.x/
- Learn about python unittest library: https://docs.python.org/2/library/unittest.html
- Another good reference link to pillow or PIL: http://code.nabla.net/doc/PIL/api/PIL/JpegImagePlugin/PIL.JpegImagePlugin.JpegImageFile.html
- A blog post on image processing in python with pillow by Joyce Echessa: https://auth0.com/blog/image-processing-in-python-with-pillow/