diff --git a/docs/index.rst b/docs/index.rst index 96ca3c3f00b1..4d86555e1273 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -149,6 +149,7 @@ vision-usage vision-client + vision-color vision-entity vision-feature vision-face diff --git a/docs/vision-color.rst b/docs/vision-color.rst new file mode 100644 index 000000000000..f2a9a53f1d8a --- /dev/null +++ b/docs/vision-color.rst @@ -0,0 +1,10 @@ +Vision Image Properties +======================= + +Image Properties Annotation +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. automodule:: google.cloud.vision.color + :members: + :undoc-members: + :show-inheritance: diff --git a/google/cloud/vision/color.py b/google/cloud/vision/color.py new file mode 100644 index 000000000000..20927f79c6be --- /dev/null +++ b/google/cloud/vision/color.py @@ -0,0 +1,191 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Image properties class representation derived from Vision API response.""" + + +class ImagePropertiesAnnotation(object): + """Representation of image properties + + :type colors: list + :param colors: List of + :class:`~google.cloud.vision.color.ColorInformation`. + """ + def __init__(self, colors): + self._colors = colors + + @classmethod + def from_api_repr(cls, response): + """Factory: construct ``ImagePropertiesAnnotation`` from a response. + + :type response: dict + :param response: Dictionary response from Vision API with image + properties data. + + :rtype: :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`. + :returns: Populated instance of ``ImagePropertiesAnnotation``. + """ + colors = [ColorInformation.from_api_repr(color) for color in + response['dominantColors']['colors']] + return cls(colors) + + @property + def colors(self): + """Colors in an image. + + :rtype: list of :class:`~google.cloud.vision.color.ColorInformation` + :returns: Populated list of ``ColorInformation``. + """ + return self._colors + + +class Color(object): + """Representation of RGBA color information. + + :type red: int + :param red: The amount of red in the color as a value in the interval + [0, 255]. + + :type green: int + :param green: The amount of green in the color as a value in the interval + [0, 255]. + + :type blue: int + :param blue: The amount of blue in the color as a value in the interval + [0, 255]. + + :type alpha: float + :param alpha: The fraction of this color that should be applied to the + pixel. + """ + def __init__(self, red, green, blue, alpha): + self._red = red + self._green = green + self._blue = blue + self._alpha = alpha + + @classmethod + def from_api_repr(cls, response): + """Factory: construct a ``Color`` from a Vision API response. + + :type response: dict + :param response: Color from API Response. + + :rtype: :class:`~google.cloud.vision.color.Color` + :returns: Instance of :class:`~google.cloud.vision.color.Color`. + """ + red = response['red'] + green = response['green'] + blue = response['blue'] + alpha = response.get('alpha') + + return cls(red, green, blue, alpha) + + @property + def red(self): + """Red component of the color. + + :rtype: int + :returns: Red RGB value. + """ + return self._red + + @property + def green(self): + """Green component of the color. + + :rtype: int + :returns: Green RGB value. + """ + return self._green + + @property + def blue(self): + """Blue component of the color. + + :rtype: int + :returns: Blue RGB value. + """ + return self._blue + + @property + def alpha(self): + """Alpha transparency level. + + :rtype: float + :returns: Alpha transparency level. + """ + return self._alpha + + +class ColorInformation(object): + """Representation of color information from API response. + + :type color: :class:`~google.cloud.vision.color.Color` + :param color: RGB components of the color. + + :type score: float + :param score: Image-specific score for this color. Value in range [0, 1]. + + :type pixel_fraction: float + :param pixel_fraction: Stores the fraction of pixels the color occupies in + the image. Value in range [0, 1]. + """ + def __init__(self, color, score, pixel_fraction): + self._color = color + self._score = score + self._pixel_fraction = pixel_fraction + + @classmethod + def from_api_repr(cls, response): + """Factory: construct ``ColorInformation`` for a color found. + + :type response: dict + :param response: Color data with extra meta information. + + :rtype: :class:`~google.cloud.vision.color.ColorInformation` + :returns: Instance of ``ColorInformation``. + """ + color = Color.from_api_repr(response['color']) + score = response['score'] + pixel_fraction = response['pixelFraction'] + + return cls(color, score, pixel_fraction) + + @property + def color(self): + """RGB components of the color. + + :rtype: :class:`~google.vision.color.Color` + :returns: Instance of ``Color``. + """ + return self._color + + @property + def score(self): + """Image-specific score for this color. Value in range [0, 1]. + + :rtype: float + :returns: Image score for this color. + """ + return self._score + + @property + def pixel_fraction(self): + """Stores the fraction of pixels the color occupies in the image. + + :rtype: float + :returns: Pixel fraction value in range [0, 1]. + """ + return self._pixel_fraction diff --git a/google/cloud/vision/entity.py b/google/cloud/vision/entity.py index 370b72781c5c..6d9ec139e0f8 100644 --- a/google/cloud/vision/entity.py +++ b/google/cloud/vision/entity.py @@ -62,7 +62,6 @@ def from_api_repr(cls, response): """ bounds = Bounds.from_api_repr(response.get('boundingPoly')) description = response['description'] - locale = response.get('locale', None) locations = [LocationInformation.from_api_repr(location) for location in response.get('locations', [])] diff --git a/google/cloud/vision/feature.py b/google/cloud/vision/feature.py index 6716b3021921..a60abea17caa 100644 --- a/google/cloud/vision/feature.py +++ b/google/cloud/vision/feature.py @@ -27,6 +27,7 @@ class FeatureTypes(object): LABEL_DETECTION = 'LABEL_DETECTION' TEXT_DETECTION = 'TEXT_DETECTION' SAFE_SEARCH_DETECTION = 'SAFE_SEARCH_DETECTION' + IMAGE_PROPERTIES = 'IMAGE_PROPERTIES' class Feature(object): diff --git a/google/cloud/vision/image.py b/google/cloud/vision/image.py index 9c70e572aac2..c8c5a5f4ac66 100644 --- a/google/cloud/vision/image.py +++ b/google/cloud/vision/image.py @@ -22,6 +22,7 @@ from google.cloud.vision.face import Face from google.cloud.vision.feature import Feature from google.cloud.vision.feature import FeatureTypes +from google.cloud.vision.color import ImagePropertiesAnnotation from google.cloud.vision.safe import SafeSearchAnnotation @@ -163,6 +164,21 @@ def detect_logos(self, limit=10): feature = Feature(FeatureTypes.LOGO_DETECTION, limit) return self._detect_annotation(feature) + def detect_properties(self, limit=10): + """Detect the color properties of an image. + + :type limit: int + :param limit: The maximum number of image properties to find. + + :rtype: list + :returns: List of + :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`. + """ + feature = Feature(FeatureTypes.IMAGE_PROPERTIES, limit) + result = self.client.annotate(self, [feature]) + response = result['imagePropertiesAnnotation'] + return ImagePropertiesAnnotation.from_api_repr(response) + def detect_safe_search(self, limit=10): """Retreive safe search properties from an image. diff --git a/unit_tests/vision/_fixtures.py b/unit_tests/vision/_fixtures.py index 6bd39f89b72a..f30b69a18a63 100644 --- a/unit_tests/vision/_fixtures.py +++ b/unit_tests/vision/_fixtures.py @@ -1,3 +1,107 @@ +IMAGE_PROPERTIES_RESPONSE = { + 'responses': [ + { + 'imagePropertiesAnnotation': { + 'dominantColors': { + 'colors': [ + { + 'color': { + 'red': 253, + 'green': 203, + 'blue': 65, + 'alpha': 0.0 + }, + 'score': 0.42258179, + 'pixelFraction': 0.025376344 + }, + { + 'color': { + 'red': 216, + 'green': 69, + 'blue': 56 + }, + 'score': 0.34945792, + 'pixelFraction': 0.026093191 + }, + { + 'color': { + 'red': 79, + 'green': 142, + 'blue': 245 + }, + 'score': 0.050921876, + 'pixelFraction': 0.014193549 + }, + { + 'color': { + 'red': 249, + 'green': 246, + 'blue': 246 + }, + 'score': 0.0059412993, + 'pixelFraction': 0.86896056 + }, + { + 'color': { + 'red': 222, + 'green': 119, + 'blue': 51 + }, + 'score': 0.0043299114, + 'pixelFraction': 0.00021505376 + }, + { + 'color': { + 'red': 226, + 'green': 138, + 'blue': 130 + }, + 'score': 0.0038594988, + 'pixelFraction': 0.00086021505 + }, + { + 'color': { + 'red': 165, + 'green': 194, + 'blue': 243 + }, + 'score': 0.0029492097, + 'pixelFraction': 0.0015053763 + }, + { + 'color': { + 'red': 231, + 'green': 169, + 'blue': 164 + }, + 'score': 0.0017002203, + 'pixelFraction': 0.00043010752 + }, + { + 'color': { + 'red': 137, + 'green': 98, + 'blue': 142 + }, + 'score': 0.0013974205, + 'pixelFraction': 0.00071684585 + }, + { + 'color': { + 'red': 239, + 'green': 179, + 'blue': 56 + }, + 'score': 0.050473157, + 'pixelFraction': 0.0022222223 + } + ] + } + } + } + ] +} + LABEL_DETECTION_RESPONSE = { 'responses': [ { diff --git a/unit_tests/vision/test_client.py b/unit_tests/vision/test_client.py index 4a35b5963bf9..fe27ded31662 100644 --- a/unit_tests/vision/test_client.py +++ b/unit_tests/vision/test_client.py @@ -248,6 +248,30 @@ def test_safe_search_detection_from_source(self): self.assertEqual('POSSIBLE', safe_search.medical) self.assertEqual('VERY_UNLIKELY', safe_search.violence) + def test_image_properties_detection_from_source(self): + from google.cloud.vision.color import ImagePropertiesAnnotation + from unit_tests.vision._fixtures import IMAGE_PROPERTIES_RESPONSE + + RETURNED = IMAGE_PROPERTIES_RESPONSE + credentials = _Credentials() + client = self._makeOne(project=self.PROJECT, credentials=credentials) + client.connection = _Connection(RETURNED) + + image = client.image(source_uri=_IMAGE_SOURCE) + image_properties = image.detect_properties() + self.assertTrue(isinstance(image_properties, + ImagePropertiesAnnotation)) + image_request = client.connection._requested[0]['data']['requests'][0] + self.assertEqual(_IMAGE_SOURCE, + image_request['image']['source']['gcs_image_uri']) + self.assertEqual(0.42258179, image_properties.colors[0].score) + self.assertEqual(0.025376344, + image_properties.colors[0].pixel_fraction) + self.assertEqual(253, image_properties.colors[0].color.red) + self.assertEqual(203, image_properties.colors[0].color.green) + self.assertEqual(65, image_properties.colors[0].color.blue) + self.assertEqual(0.0, image_properties.colors[0].color.alpha) + class TestVisionRequest(unittest.TestCase): def _getTargetClass(self):