From de4e70db012792e2fde64a057be16a6d260fb76e Mon Sep 17 00:00:00 2001 From: Neeraj Komuravalli Date: Mon, 10 Aug 2020 18:48:53 +0530 Subject: [PATCH 1/3] Adding first version of `resize_with_original_aspect_ratio` (not fully tested) --- .../toolkits/image_analysis/image_analysis.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/python/turicreate/toolkits/image_analysis/image_analysis.py b/src/python/turicreate/toolkits/image_analysis/image_analysis.py index 705cdde023..32ba58eb80 100644 --- a/src/python/turicreate/toolkits/image_analysis/image_analysis.py +++ b/src/python/turicreate/toolkits/image_analysis/image_analysis.py @@ -192,6 +192,112 @@ def resize(image, width, height, channels=None, decode=False, resample="nearest" "Cannot call 'resize' on objects that are not either an Image or SArray of Images" ) +def resize_with_original_aspect_ratio(image, min_side_length=None, max_side_length=None, channels=None, decode=False, resample="nearest"): + """ + Resizes the image or SArray of Images to a specific width or height while maintaining the aspect ratio + + Parameters + ---------- + + image : turicreate.Image | SArray + The image or SArray of images to be resized. + min_side_length : int + The width the image is resized to. + max_side_length : int + The height the image is resized to. + channels : int, optional + The number of channels the image is resized to. 1 channel + corresponds to grayscale, 3 channels corresponds to RGB, and 4 + channels corresponds to RGBA images. + decode : bool, optional + Whether to store the resized image in decoded format. Decoded takes + more space, but makes the resize and future operations on the image faster. + resample : 'nearest' or 'bilinear' + Specify the resampling filter: + + - ``'nearest'``: Nearest neigbhor, extremely fast + - ``'bilinear'``: Bilinear, fast and with less aliasing artifacts + + Returns + ------- + out : turicreate.Image + Returns a resized Image object with original aspect ratio. + + Notes + ----- + Grayscale Images -> Images with one channel, representing a scale from + white to black + + RGB Images -> Images with 3 channels, with each pixel having Green, Red, + and Blue values. + + RGBA Images -> An RGB image with an opacity channel. + + Examples + -------- + + Resize a single image + + >>> img = turicreate.Image('https://static.turi.com/datasets/images/sample.jpg') + >>> resized_img = turicreate.image_analysis.resize_with_original_aspect_ratio(img,min_side_length=500,max_side_length=None, channels=1) + >>> resized_img = turicreate.image_analysis.resize_with_original_aspect_ratio(img,min_side_length=None,max_side_length=500, channels=1) + + Resize an SArray of images + + >>> url ='https://static.turi.com/datasets/images/nested' + >>> image_sframe = turicreate.image_analysis.load_images(url, "auto", with_path=False, + ... recursive=True) + >>> image_sarray = image_sframe["image"] + >>> resized_images = turicreate.image_analysis.resize(image_sarray, min_side_length=500,max_side_length=None, channels=1) + >>> resized_images = turicreate.image_analysis.resize(image_sarray, min_side_length=None,max_side_length=500, channels=1) + """ + if min_side_length is None and max_side_length is None: + raise ValueError("Cannot resize when neither `min_side_length` or `max_side_length` is not provided") + + if min_side_length > 0 and max_side_length > 0: + raise ValueError("Cannot provide both parameters `min_side_length` and `max_side_length` as only one is required to maintain the aspect ratio") + + if min_side_length is not None and min_side_length > 0: + side_length_type = "min_length" + side_length = min_side_length + elif max_side_length is not None and max_side_length > 0: + side_length_type = "max_length" + side_length = max_side_length + else: + raise ValueError("Need to provode either the `min_side_length` or `max_side_length` to resize while maintaining the aspect ratio") + + if type(image) is _Image: + new_image_shape = _get_new_image_dimensions_with_original_aspect_ratio(image, side_length_type, side_length) + return resize(image, new_image_shape[0], new_image_shape[1], channels=channels, decode=decode, resample=resample) + elif type(image) is _SArray: + new_image_shape_sarray = image.apply( + lambda x: _get_new_image_dimensions_with_original_aspect_ratio(x, side_length_type, side_length) + ) + image_with_new_dims_sframe = _tc.SFrame({"image": image, "new_shape": new_image_shape_sarray}) + return image_with_new_dims_sframe.apply( + lambda x: resize(x['image'], x['new_shape'][0], x['new_shape'][0], channels=channels, decode=decode, resample=resample) + ) + else: + raise ValueError( + "Cannot call 'resize' on objects that are not either an Image or SArray of Images" + ) + + +def _get_new_image_dimensions_with_original_aspect_ratio(image, side_length_type, side_length): + + image_shape = (image.width, image.height) + aspect_ratio = image_shape[0]/image_shape[1] + if aspect_ratio > 1: + if side_length_type == "max_length": + return (side_length, int(side_length/aspect_ratio)) + else: + return (int(side_length*aspect_ratio), side_length) + else: + if side_length_type == "max_length": + return (int(side_length*aspect_ratio), side_length) + else: + return (side_length, int(side_length/aspect_ratio)) + def get_deep_features(images, model_name, batch_size=64, verbose=True): """ From 663a4ce0e6414e8e3fec46cbfe40a517a1669a58 Mon Sep 17 00:00:00 2001 From: Neeraj Komuravalli Date: Tue, 11 Aug 2020 16:14:49 +0530 Subject: [PATCH 2/3] Adding first build of resize_annotations (not fully tested) --- .../toolkits/image_analysis/image_analysis.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/python/turicreate/toolkits/image_analysis/image_analysis.py b/src/python/turicreate/toolkits/image_analysis/image_analysis.py index 32ba58eb80..cb4a0b9df9 100644 --- a/src/python/turicreate/toolkits/image_analysis/image_analysis.py +++ b/src/python/turicreate/toolkits/image_analysis/image_analysis.py @@ -299,6 +299,49 @@ def _get_new_image_dimensions_with_original_aspect_ratio(image, side_length_type return (side_length, int(side_length/aspect_ratio)) +def resize_annotations(original_image_shape, annotations, target_image_shape): + """ + Resize annotations to any target image dimensions + + Parameters + ---------- + original_image_shape : tuple | list + Original image dimesions in the format of (width, height) + annotations : list + Original annotations of the data. + This should be a list of dictionaries , with + each dictionary representing a bounding box of an object instance. Here + is an example of the annotations for a single image with two object + instances:: + + [{'label': 'dog', + 'type': 'rectangle', + 'coordinates': {'x': 223, 'y': 198, + 'width': 130, 'height': 230}}, + {'label': 'cat', + 'type': 'rectangle', + 'coordinates': {'x': 40, 'y': 73, + 'width': 80, 'height': 123}}] + + The value for `x` is the horizontal center of the box paired with + `width` and `y` is the vertical center of the box paired with `height`. + 'None' (the default) indicates the only list column in `dataset` should + be used for the annotations. + For more information on annotations format refer to `https://apple.github.io/turicreate/docs/userguide/object_detection/` + target_image_shape : tuple | list + Target image dimesions in the format of (width, height) + """ + if type(annotations) is not list: + raise ValueError("annotations are expected in the form of list") + + for ann_index in range(len(annotations)): + annotations[ann_index]["coordinates"]["height"] = annotations[ann_index]["coordinates"]["height"]*target_image_shape[1]/original_image_shape[1] + annotations[ann_index]["coordinates"]["width"] = annotations[ann_index]["coordinates"]["width"]*target_image_shape[0]/original_image_shape[0] + annotations[ann_index]["coordinates"]["x"] = annotations[ann_index]["coordinates"]["x"]*target_image_shape[0]/original_image_shape[0] + annotations[ann_index]["coordinates"]["y"] = annotations[ann_index]["coordinates"]["y"]*target_image_shape[1]/original_image_shape[1] + return annotations + + def get_deep_features(images, model_name, batch_size=64, verbose=True): """ Extracts features from images from a specific model. From 498e429eb45bc9e7f23439b989dccc77e96a1f58 Mon Sep 17 00:00:00 2001 From: Neeraj Komuravalli Date: Sat, 22 Aug 2020 19:50:36 +0530 Subject: [PATCH 3/3] Testing and fixing the issues --- .../toolkits/image_analysis/image_analysis.py | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/python/turicreate/toolkits/image_analysis/image_analysis.py b/src/python/turicreate/toolkits/image_analysis/image_analysis.py index cb4a0b9df9..c4fc826a7c 100644 --- a/src/python/turicreate/toolkits/image_analysis/image_analysis.py +++ b/src/python/turicreate/toolkits/image_analysis/image_analysis.py @@ -202,9 +202,15 @@ def resize_with_original_aspect_ratio(image, min_side_length=None, max_side_leng image : turicreate.Image | SArray The image or SArray of images to be resized. min_side_length : int - The width the image is resized to. + The size of the minimum side of the image to be resized while maintaining aspect ratio. + For ex: + if the image shape is : 720 * 960 i.e => the aspect ratio is 3:4 + so if you set the min_side_length to 600, then the resized image dimension would be => 600 * 800 max_side_length : int - The height the image is resized to. + The size of the maximum side of the image to be resized while maintaining aspect ratio. + For ex: + if the image shape is : 720 * 960 i.e => the aspect ratio is 3:4 + so if you set the max_side_length to 600, then the resized image dimension would be => 450 * 600 channels : int, optional The number of channels the image is resized to. 1 channel corresponds to grayscale, 3 channels corresponds to RGB, and 4 @@ -251,18 +257,28 @@ def resize_with_original_aspect_ratio(image, min_side_length=None, max_side_leng >>> resized_images = turicreate.image_analysis.resize(image_sarray, min_side_length=500,max_side_length=None, channels=1) >>> resized_images = turicreate.image_analysis.resize(image_sarray, min_side_length=None,max_side_length=500, channels=1) """ + + from ...data_structures.sarray import SArray as _SArray + import turicreate as _tc + if min_side_length is None and max_side_length is None: raise ValueError("Cannot resize when neither `min_side_length` or `max_side_length` is not provided") - if min_side_length > 0 and max_side_length > 0: + if (min_side_length is not None and min_side_length > 0) and (max_side_length is not None and max_side_length > 0): raise ValueError("Cannot provide both parameters `min_side_length` and `max_side_length` as only one is required to maintain the aspect ratio") - - if min_side_length is not None and min_side_length > 0: - side_length_type = "min_length" - side_length = min_side_length - elif max_side_length is not None and max_side_length > 0: - side_length_type = "max_length" - side_length = max_side_length + + if min_side_length is not None: + if min_side_length > 0: + side_length_type = "min_length" + side_length = min_side_length + else: + raise ValueError("Value of min_side_length parameter cannot be negetive") + elif max_side_length is not None: + if max_side_length > 0: + side_length_type = "max_length" + side_length = max_side_length + else: + raise ValueError("Value of max_side_length parameter cannot be negetive") else: raise ValueError("Need to provode either the `min_side_length` or `max_side_length` to resize while maintaining the aspect ratio") @@ -275,7 +291,7 @@ def resize_with_original_aspect_ratio(image, min_side_length=None, max_side_leng ) image_with_new_dims_sframe = _tc.SFrame({"image": image, "new_shape": new_image_shape_sarray}) return image_with_new_dims_sframe.apply( - lambda x: resize(x['image'], x['new_shape'][0], x['new_shape'][0], channels=channels, decode=decode, resample=resample) + lambda x: resize(x['image'], x['new_shape'][0], x['new_shape'][1], channels=channels, decode=decode, resample=resample) ) else: raise ValueError(