Skip to content

Commit

Permalink
Add GAPIC support for image properties detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
daspecster committed Jan 20, 2017
1 parent 309fa8d commit c9b6732
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 63 deletions.
8 changes: 4 additions & 4 deletions docs/vision-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,13 @@ image and determine the dominant colors in the image.
>>> client = vision.Client()
>>> with open('./image.jpg', 'rb') as image_file:
... image = client.image(content=image_file.read())
>>> results = image.detect_properties()
>>> colors = results[0].colors
>>> properties = image.detect_properties()
>>> colors = properties.colors
>>> first_color = colors[0]
>>> first_color.red
244
244.0
>>> first_color.blue
134
134.0
>>> first_color.score
0.65519291
>>> first_color.pixel_fraction
Expand Down
26 changes: 7 additions & 19 deletions system_tests/vision.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,13 @@ def tearDown(self):
value.delete()

def _assert_color(self, color):
self.assertIsInstance(color.red, int)
self.assertIsInstance(color.green, int)
self.assertIsInstance(color.blue, int)
self.assertIsInstance(color.red, float)
self.assertIsInstance(color.green, float)
self.assertIsInstance(color.blue, float)
self.assertIsInstance(color.alpha, float)
self.assertNotEqual(color.red, 0.0)
self.assertNotEqual(color.green, 0.0)
self.assertNotEqual(color.blue, 0.0)
self.assertIsInstance(color.alpha, float)

def _assert_properties(self, image_property):
from google.cloud.vision.color import ImagePropertiesAnnotation
Expand All @@ -493,19 +493,13 @@ def _assert_properties(self, image_property):
self.assertNotEqual(color_info.score, 0.0)

def test_detect_properties_content(self):
self._pb_not_implemented_skip(
'gRPC not implemented for image properties detection.')
client = Config.CLIENT
with open(FACE_FILE, 'rb') as image_file:
image = client.image(content=image_file.read())
properties = image.detect_properties()
self.assertEqual(len(properties), 1)
image_property = properties[0]
self._assert_properties(image_property)
self._assert_properties(properties)

def test_detect_properties_gcs(self):
self._pb_not_implemented_skip(
'gRPC not implemented for image properties detection.')
client = Config.CLIENT
bucket_name = Config.TEST_BUCKET.name
blob_name = 'faces.jpg'
Expand All @@ -518,16 +512,10 @@ def test_detect_properties_gcs(self):

image = client.image(source_uri=source_uri)
properties = image.detect_properties()
self.assertEqual(len(properties), 1)
image_property = properties[0]
self._assert_properties(image_property)
self._assert_properties(properties)

def test_detect_properties_filename(self):
self._pb_not_implemented_skip(
'gRPC not implemented for image properties detection.')
client = Config.CLIENT
image = client.image(filename=FACE_FILE)
properties = image.detect_properties()
self.assertEqual(len(properties), 1)
image_property = properties[0]
self._assert_properties(image_property)
self._assert_properties(properties)
34 changes: 25 additions & 9 deletions vision/google/cloud/vision/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Annotations management for Vision API responses."""

import six

from google.cloud.vision.color import ImagePropertiesAnnotation
from google.cloud.vision.entity import EntityAnnotation
Expand Down Expand Up @@ -86,11 +87,11 @@ def from_api_repr(cls, response):
:rtype: :class:`~google.cloud.vision.annotations.Annotations`
:returns: An instance of ``Annotations`` with detection types loaded.
"""
annotations = {}
for feature_type, annotation in response.items():
curr_feature = annotations.setdefault(_KEY_MAP[feature_type], [])
curr_feature.extend(
_entity_from_response_type(feature_type, annotation))
annotations = {
_KEY_MAP[feature_type]: _entity_from_response_type(
feature_type, annotation)
for feature_type, annotation in six.iteritems(response)
}
return cls(**annotations)

@classmethod
Expand Down Expand Up @@ -123,12 +124,14 @@ def _process_image_annotations(image):
'labels': _make_entity_from_pb(image.label_annotations),
'landmarks': _make_entity_from_pb(image.landmark_annotations),
'logos': _make_entity_from_pb(image.logo_annotations),
'properties': _make_image_properties_from_pb(
image.image_properties_annotation),
'texts': _make_entity_from_pb(image.text_annotations),
}


def _make_entity_from_pb(annotations):
"""Create an entity from a gRPC response.
"""Create an entity from a protobuf response.
:type annotations:
:class:`~google.cloud.grpc.vision.v1.image_annotator_pb2.EntityAnnotation`
Expand All @@ -141,7 +144,7 @@ def _make_entity_from_pb(annotations):


def _make_faces_from_pb(faces):
"""Create face objects from a gRPC response.
"""Create face objects from a protobuf response.
:type faces:
:class:`~google.cloud.grpc.vision.v1.image_annotator_pb2.FaceAnnotation`
Expand All @@ -153,6 +156,20 @@ def _make_faces_from_pb(faces):
return [Face.from_pb(face) for face in faces]


def _make_image_properties_from_pb(image_properties):
"""Create ``ImageProperties`` object from a protobuf response.
:type image_properties: :class:`~google.cloud.grpc.vision.v1.\
image_annotator_pb2.ImagePropertiesAnnotation`
:param image_properties: Protobuf instance of
``ImagePropertiesAnnotation``.
:rtype: list or ``None``
:returns: List of ``ImageProperties`` or ``None``.
"""
return ImagePropertiesAnnotation.from_pb(image_properties)


def _entity_from_response_type(feature_type, results):
"""Convert a JSON result to an entity type based on the feature.
Expand All @@ -168,8 +185,7 @@ def _entity_from_response_type(feature_type, results):
detected_objects.extend(
Face.from_api_repr(face) for face in results)
elif feature_type == _IMAGE_PROPERTIES_ANNOTATION:
detected_objects.append(
ImagePropertiesAnnotation.from_api_repr(results))
return ImagePropertiesAnnotation.from_api_repr(results)
elif feature_type == _SAFE_SEARCH_ANNOTATION:
detected_objects.append(SafeSearchAnnotation.from_api_repr(results))
else:
Expand Down
96 changes: 70 additions & 26 deletions vision/google/cloud/vision/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,37 @@ def __init__(self, colors):
self._colors = colors

@classmethod
def from_api_repr(cls, response):
def from_api_repr(cls, image_properties):
"""Factory: construct ``ImagePropertiesAnnotation`` from a response.
:type response: dict
:param response: Dictionary response from Vision API with image
properties data.
:type image_properties: dict
:param image_properties: Dictionary response from Vision API with image
properties data.
:rtype: list of
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
:returns: List of ``ImagePropertiesAnnotation``.
"""
colors = image_properties.get('dominantColors', {}).get('colors', ())
return cls([ColorInformation.from_api_repr(color)
for color in colors])

@classmethod
def from_pb(cls, image_properties):
"""Factory: construct ``ImagePropertiesAnnotation`` from a response.
:type image_properties: :class:`~google.cloud.grpc.vision.v1.\
image_annotator_pb2.ImageProperties`
:param image_properties: Protobuf response from Vision API with image
properties data.
:rtype: :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
:returns: Populated instance of ``ImagePropertiesAnnotation``.
:rtype: list of
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`
:returns: List of ``ImagePropertiesAnnotation``.
"""
raw_colors = response.get('dominantColors', {}).get('colors', ())
colors = [ColorInformation.from_api_repr(color)
for color in raw_colors]
return cls(colors)
colors = getattr(image_properties.dominant_colors, 'colors', ())
if len(colors) > 0:
return cls([ColorInformation.from_pb(color) for color in colors])

@property
def colors(self):
Expand All @@ -54,17 +71,17 @@ def colors(self):
class Color(object):
"""Representation of RGBA color information.
:type red: int
:type red: float
:param red: The amount of red in the color as a value in the interval
[0, 255].
[0.0, 255.0].
:type green: int
:type green: float
:param green: The amount of green in the color as a value in the interval
[0, 255].
[0.0, 255.0].
:type blue: int
:type blue: float
:param blue: The amount of blue in the color as a value in the interval
[0, 255].
[0.0, 255.0].
:type alpha: float
:param alpha: The fraction of this color that should be applied to the
Expand All @@ -86,13 +103,25 @@ def from_api_repr(cls, response):
:rtype: :class:`~google.cloud.vision.color.Color`
:returns: Instance of :class:`~google.cloud.vision.color.Color`.
"""
red = response.get('red', 0)
green = response.get('green', 0)
blue = response.get('blue', 0)
red = float(response.get('red', 0.0))
green = float(response.get('green', 0.0))
blue = float(response.get('blue', 0.0))
alpha = response.get('alpha', 0.0)

return cls(red, green, blue, alpha)

@classmethod
def from_pb(cls, color):
"""Factory: construct a ``Color`` from a protobuf response.
:type color: :module: `google.type.color_pb2`
:param color: ``Color`` from API Response.
:rtype: :class:`~google.cloud.vision.color.Color`
:returns: Instance of :class:`~google.cloud.vision.color.Color`.
"""
return cls(color.red, color.green, color.blue, color.alpha.value)

@property
def red(self):
"""Red component of the color.
Expand Down Expand Up @@ -149,19 +178,34 @@ def __init__(self, color, score, pixel_fraction):
self._pixel_fraction = pixel_fraction

@classmethod
def from_api_repr(cls, response):
"""Factory: construct ``ColorInformation`` for a color found.
def from_api_repr(cls, color_information):
"""Factory: construct ``ColorInformation`` for a color.
:type response: dict
:param response: Color data with extra meta information.
:type color_information: dict
:param color_information: Color data with extra meta information.
:rtype: :class:`~google.cloud.vision.color.ColorInformation`
:returns: Instance of ``ColorInformation``.
"""
color = Color.from_api_repr(response.get('color'))
score = response.get('score')
pixel_fraction = response.get('pixelFraction')
color = Color.from_api_repr(color_information.get('color', {}))
score = color_information.get('score')
pixel_fraction = color_information.get('pixelFraction')
return cls(color, score, pixel_fraction)

@classmethod
def from_pb(cls, color_information):
"""Factory: construct ``ColorInformation`` for a color.
:type color_information: :class:`~google.cloud.grpc.vision.v1.\
image_annotator_pb2.ColorInfo`
:param color_information: Color data with extra meta information.
:rtype: :class:`~google.cloud.vision.color.ColorInformation`
:returns: Instance of ``ColorInformation``.
"""
color = Color.from_pb(color_information.color)
score = color_information.score
pixel_fraction = color_information.pixel_fraction
return cls(color, score, pixel_fraction)

@property
Expand Down
33 changes: 32 additions & 1 deletion vision/unit_tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def test_from_pb(self):
self.assertEqual(annotations.landmarks, [])
self.assertEqual(annotations.texts, [])
self.assertEqual(annotations.safe_searches, ())
self.assertEqual(annotations.properties, ())
self.assertIsNone(annotations.properties)


class Test__make_entity_from_pb(unittest.TestCase):
Expand Down Expand Up @@ -122,6 +122,37 @@ def test_it(self):
self.assertIsInstance(faces[0], Face)


class Test__make_image_properties_from_pb(unittest.TestCase):
def _call_fut(self, annotations):
from google.cloud.vision.annotations import (
_make_image_properties_from_pb)
return _make_image_properties_from_pb(annotations)

def test_it(self):
from google.cloud.grpc.vision.v1 import image_annotator_pb2
from google.protobuf.wrappers_pb2 import FloatValue
from google.type.color_pb2 import Color

alpha = FloatValue(value=1.0)
color_pb = Color(red=1.0, green=2.0, blue=3.0, alpha=alpha)
color_info_pb = image_annotator_pb2.ColorInfo(color=color_pb,
score=1.0,
pixel_fraction=1.0)
dominant_colors = image_annotator_pb2.DominantColorsAnnotation(
colors=[color_info_pb])

image_properties_pb = image_annotator_pb2.ImageProperties(
dominant_colors=dominant_colors)

image_properties = self._call_fut(image_properties_pb)
self.assertEqual(image_properties.colors[0].pixel_fraction, 1.0)
self.assertEqual(image_properties.colors[0].score, 1.0)
self.assertEqual(image_properties.colors[0].color.red, 1.0)
self.assertEqual(image_properties.colors[0].color.green, 2.0)
self.assertEqual(image_properties.colors[0].color.blue, 3.0)
self.assertEqual(image_properties.colors[0].color.alpha, 1.0)


class Test__process_image_annotations(unittest.TestCase):
def _call_fut(self, image):
from google.cloud.vision.annotations import _process_image_annotations
Expand Down
2 changes: 1 addition & 1 deletion vision/unit_tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ def test_image_properties_detection_from_source(self):
client._connection = _Connection(RETURNED)

image = client.image(source_uri=IMAGE_SOURCE)
image_properties = image.detect_properties()[0]
image_properties = image.detect_properties()
self.assertIsInstance(image_properties, ImagePropertiesAnnotation)
image_request = client._connection._requested[0]['data']['requests'][0]
self.assertEqual(IMAGE_SOURCE,
Expand Down
Loading

0 comments on commit c9b6732

Please sign in to comment.