8000 Merge pull request #2937 from daspecster/vision-add-image-properties-… · richkadel/google-cloud-python@854e484 · GitHub
[go: up one dir, main page]

Skip to content

Commit 854e484

Browse files
authored
Merge pull request googleapis#2937 from daspecster/vision-add-image-properties-from-pb
Vision: Add gRPC support for image properties.
2 parents 03e14dc + c47b839 commit 854e484

File tree

7 files changed

+225
-63
lines changed

7 files changed

+225
-63
lines changed

docs/vision-usage.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,13 @@ image and determine the dominant colors in the image.
271271
>>> client = vision.Client()
272272
>>> with open('./image.jpg', 'rb') as image_file:
273273
... image = client.image(content=image_file.read())
274-
>>> results = image.detect_properties()
275-
>>> colors = results[0].colors
274+
>>> properties = image.detect_properties()
275+
>>> colors = properties.colors
276276
>>> first_color = colors[0]
277277
>>> first_color.red
278-
244
278+
244.0
279279
>>> first_color.blue
280-
134
280+
134.0
281281
>>> first_color.score
282282
0.65519291
283283
>>> first_color.pixel_fraction

system_tests/vision.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,13 @@ def tearDown(self):
474474
value.delete()
475475

476476
def _assert_color(self, color):
477-
self.assertIsInstance(color.red, int)
478-
self.assertIsInstance(color.green, int)
479-
self.assertIsInstance(color.blue, int)
477+
self.assertIsInstance(color.red, float)
478+
self.assertIsInstance(color.green, float)
479+
self.assertIsInstance(color.blue, float)
480+
self.assertIsInstance(color.alpha, float)
480481
self.assertNotEqual(color.red, 0.0)
481482
self.assertNotEqual(color.green, 0.0)
482483
self.assertNotEqual(color.blue, 0.0)
483-
self.assertIsInstance(color.alpha, float)
484484

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

495495
def test_detect_properties_content(self):
496-
self._pb_not_implemented_skip(
497-
'gRPC not implemented for image properties detection.')
498496
client = Config.CLIENT
499497
with open(FACE_FILE, 'rb') as image_file:
500498
image = client.image(content=image_file.read())
501499
properties = image.detect_properties()
502-
self.assertEqual(len(properties), 1)
503-
image_property = properties[0]
504-
self._assert_properties(image_property)
500+
self._assert_properties(properties)
505501

506502
def test_detect_properties_gcs(self):
507-
self._pb_not_implemented_skip(
508-
'gRPC not implemented for image properties detection.')
509503
client = Config.CLIENT
510504
bucket_name = Config.TEST_BUCKET.name
511505
blob_name = 'faces.jpg'
@@ -518,16 +512,10 @@ def test_detect_properties_gcs(self):
518512

519513
image = client.image(source_uri=source_uri)
520514
properties = image.detect_properties()
521-
self.assertEqual(len(properties), 1)
522-
image_property = properties[0]
523-
self._assert_properties(image_property)
515+
self._assert_properties(properties)
524516

525517
def test_detect_properties_filename(self):
526-
self._pb_not_implemented_skip(
527-
'gRPC not implemented for image properties detection.')
528518
client = Config.CLIENT
529519
image = client.image(filename=FACE_FILE)
530520
properties = image.detect_properties()
531-
self.assertEqual(len(properties), 1)
532-
image_property = properties[0]
533-
self._assert_properties(image_property)
521+
self._assert_properties(properties)

vision/google/cloud/vision/annotations.py

Lines changed: 25 additions & 9 deletions
+
:param image_properties: Protobuf instance of
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

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

17+
import six
1718

1819
from google.cloud.vision.color import ImagePropertiesAnnotation
1920
from google.cloud.vision.entity import EntityAnnotation
@@ -86,11 +87,11 @@ def from_api_repr(cls, response):
8687
:rtype: :class:`~google.cloud.vision.annotations.Annotations`
8788
:returns: An instance of ``Annotations`` with detection types loaded.
8889
"""
89-
annotations = {}
90-
for feature_type, annotation in response.items():
91-
curr_feature = annotations.setdefault(_KEY_MAP[feature_type], [])
92-
curr_feature.extend(
93-
_entity_from_response_type(feature_type, annotation))
90+
annotations = {
91+
_KEY_MAP[feature_type]: _entity_from_response_type(
92+
feature_type, annotation)
93+
for feature_type, annotation in six.iteritems(response)
94+
}
9495
return cls(**annotations)
9596

9697
@classmethod
@@ -123,12 +124,14 @@ def _process_image_annotations(image):
123124
'labels': _make_entity_from_pb(image.label_annotations),
124125
'landmarks': _make_entity_from_pb(image.landmark_annotations),
125126
'logos': _make_entity_from_pb(image.logo_annotations),
127+
'properties': _make_image_properties_from_pb(
128+
image.image_properties_annotation),
126129
'texts': _make_entity_from_pb(image.text_annotations),
127130
}
128131

129132

130133
def _make_entity_from_pb(annotations):
131-
"""Create an entity from a gRPC response.
134+
"""Create an entity from a protobuf response.
132135
133136
:type annotations:
134137
:class:`~google.cloud.grpc.vision.v1.image_annotator_pb2.EntityAnnotation`
@@ -141,7 +144,7 @@ def _make_entity_from_pb(annotations):
141144

142145

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

155158

159+
def _make_image_properties_from_pb(image_properties):
160+
"""Create ``ImageProperties`` object from a protobuf response.
161+
162+
:type image_properties: :class:`~google.cloud.grpc.vision.v1.\
163+
image_annotator_pb2.ImagePropertiesAnnotation`
164
165+
``ImagePropertiesAnnotation``.
166+
167+
:rtype: list or ``None``
168+
:returns: List of ``ImageProperties`` or ``None``.
169+
"""
170+
return ImagePropertiesAnnotation.from_pb(image_properties)
171+
172+
156173
def _entity_from_response_type(feature_type, results):
157174
"""Convert a JSON result to an entity type based on the feature.
158175
@@ -168,8 +185,7 @@ def _entity_from_response_type(feature_type, results):
168185
detected_objects.extend(
169186
Face.from_api_repr(face) for face in results)
170187
elif feature_type == _IMAGE_PROPERTIES_ANNOTATION:
171-
detected_objects.append(
172-
ImagePropertiesAnnotation.from_api_repr(results))
188+
return ImagePropertiesAnnotation.from_api_repr(results)
173189
elif feature_type == _SAFE_SEARCH_ANNOTATION:
174190
detected_objects.append(SafeSearchAnnotation.from_api_repr(results))
175191
else:

vision/google/cloud/vision/color.py

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,37 @@ def __init__(self, colors):
2626
self._colors = colors
2727

2828
@classmethod
29-
def from_api_repr(cls, response):
29+
def from_api_repr(cls, image_properties):
3030
"""Factory: construct ``ImagePropertiesAnnotation`` from a response.
3131
32-
:type response: dict
33-
:param response: Dictionary response from Vision API with image
34-
properties data.
32+
:type image_properties: dict
33+
:param image_properties: Dictionary response from Vision API with image
34+
properties data.
35+
36+
:rtype: list of
37+
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
38+
:returns: List of ``ImagePropertiesAnnotation``.
39+
"""
40+
colors = image_properties.get('dominantColors', {}).get('colors', ())
41+
return cls([ColorInformation.from_api_repr(color)
42+
for color in colors])
43+
44+
@classmethod
45+
def from_pb(cls, image_properties):
46+
"""Factory: construct ``ImagePropertiesAnnotation`` from a response.
47+
48+
:type image_properties: :class:`~google.cloud.grpc.vision.v1.\
49+
image_annotator_pb2.ImageProperties`
50+
:param image_properties: Protobuf response from Vision API with image
51+
properties data.
3552
36-
:rtype: :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
37-
:returns: Populated instance of ``ImagePropertiesAnnotation``.
53+
:rtype: list of
54+
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`
55+
:returns: List of ``ImagePropertiesAnnotation``.
3856
"""
39-
raw_colors = response.get('dominantColors', {}).get('colors', ())
40-
colors = [ColorInformation.from_api_repr(color)
41-
for color in raw_colors]
42-
return cls(colors)
57+
colors = getattr(image_properties.dominant_colors, 'colors', ())
58+
if len(colors) > 0:
59+
return cls([ColorInformation.from_pb(color) for color in colors])
4360

4461
@property
4562
def colors(self):
@@ -54,17 +71,17 @@ def colors(self):
5471
class Color(object):
5572
"""Representation of RGBA color information.
5673
57-
:type red: int
74+
:type red: float
5875
:param red: The amount of red in the color as a value in the interval
59-
[0, 255].
76+
[0.0, 255.0].
6077
61-
:type green: int
78+
:type green: float
6279
:param green: The amount of green in the color as a value in the interval
63-
[0, 255].
80+
[0.0, 255.0].
6481
65-
:type blue: int
82+
:type blue: float
6683
:param blue: The amount of blue in the color as a value in the interval
67-
[0, 255].
84+
[0.0, 255.0].
6885
6986
:type alpha: float
7087
:param alpha: The fraction of this color that should be applied to the
@@ -86,13 +103,25 @@ def from_api_repr(cls, response):
86103
:rtype: :class:`~google.cloud.vision.color.Color`
87104
:returns: Instance of :class:`~google.cloud.vision.color.Color`.
88105
"""
89-
red = response.get('red', 0)
90-
green = response.get('green', 0)
91-
blue = response.get('blue', 0)
106+
red = float(response.get('red', 0.0))
107+
green = float(response.get('green', 0.0))
108+
blue = float(response.get('blue', 0.0))
92109
alpha = response.get('alpha', 0.0)
93110

94111
return cls(red, green, blue, alpha)
95112

113+
@classmethod
114+
def from_pb(cls, color):
115+
"""Factory: construct a ``Color`` from a protobuf response.
116+
117+
:type color: :module: `google.type.color_pb2`
118+
:param color: ``Color`` from API Response.
119+
120+
:rtype: :class:`~google.cloud.vision.color.Color`
121+
:returns: Instance of :class:`~google.cloud.vision.color.Color`.
122+
"""
123+
return cls(color.red, color.green, color.blue, color.alpha.value)
124+
96125
@property
97126
def red(self):
98127
"""Red component of the color.
@@ -149,19 +178,34 @@ def __init__(self, color, score, pixel_fraction):
149178
self._pixel_fraction = pixel_fraction
150179

151180
@classmethod
152-
def from_api_repr(cls, response):
153-
"""Factory: construct ``ColorInformation`` for a color found.
181+
def from_api_repr(cls, color_information):
182+
"""Factory: construct ``ColorInformation`` for a color.
154183
155-
:type response: dict
156-
:param response: Color data with extra meta information.
184+
:type color_information: dict
185+
:param color_information: Color data with extra meta information.
157186
158187
:rtype: :class:`~google.cloud.vision.color.ColorInformation`
159188
:returns: Instance of ``ColorInformation``.
160189
"""
161-
color = Color.from_api_repr(response.get('color'))
162-
score = response.get('score')
163-
pixel_fraction = response.get('pixelFraction')
190+
color = Color.from_api_repr(color_information.get('color', {}))
191+
score = color_information.get('score')
192+
pixel_fraction = color_information.get('pixelFraction')
193+
return cls(color, score, pixel_fraction)
164194

195+
@classmethod
196+
def from_pb(cls, color_information):
197+
"""Factory: construct ``ColorInformation`` for a color.
198+
199+
:type color_information: :class:`~google.cloud.grpc.vision.v1.\
200+
image_annotator_pb2.ColorInfo`
201+
:param color_information: Color data with extra meta information.
202+
203+
:rtype: :class:`~google.cloud.vision.color.ColorInformation`
204+
:returns: Instance of ``ColorInformation``.
205+
"""
206+
color = Color.from_pb(color_information.color)
207+
score = color_information.score
208+
pixel_fraction = color_information.pixel_fraction
165209
return cls(color, score, pixel_fraction)
166210

167211
@property

vision/unit_tests/test_annotations.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def test_from_pb(self):
7777
self.assertEqual(annotations.landmarks, [])
7878
self.assertEqual(annotations.texts, [])
7979
self.assertEqual(annotations.safe_searches, ())
80-
self.assertEqual(annotations.properties, ())
80+
self.assertIsNone(annotations.properties)
8181

8282

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

124124

125+
class Test__make_image_properties_from_pb(unittest.TestCase):
126+
def _call_fut(self, annotations):
127+
from google.cloud.vision.annotations import (
128+
_make_image_properties_from_pb)
129+
return _make_image_properties_from_pb(annotations)
130+
131+
def test_it(self):
132+
from google.cloud.grpc.vision.v1 import image_annotator_pb2
133+
from google.protobuf.wrappers_pb2 import FloatValue
134+
from google.type.color_pb2 import Color
135+
136+
alpha = FloatValue(value=1.0)
137+
color_pb = Color(red=1.0, green=2.0, blue=3.0, alpha=alpha)
138+
color_info_pb = image_annotator_pb2.ColorInfo(color=color_pb,
139+
score=1.0,
140+
pixel_fraction=1.0)
141+
dominant_colors = image_annotator_pb2.DominantColorsAnnotation(
142+
colors=[color_info_pb])
143+
144+
image_properties_pb = image_annotator_pb2.ImageProperties(
145+
dominant_colors=dominant_colors)
146+
147+
image_properties = self._call_fut(image_properties_pb)
148+
self.assertEqual(image_properties.colors[0].pixel_fraction, 1.0)
149+
self.assertEqual(image_properties.colors[0].score, 1.0)
150+
self.assertEqual(image_properties.colors[0].color.red, 1.0)
151+
self.assertEqual(image_properties.colors[0].color.green, 2.0)
152+
self.assertEqual(image_properties.colors[0].color.blue, 3.0)
153+
self.assertEqual(image_properties.colors[0].color.alpha, 1.0)
154+
155+
125156
class Test__process_image_annotations(unittest.TestCase):
126157
def _call_fut(self, image):
127158
from google.cloud.vision.annotations import _process_image_annotations

vision/unit_tests/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def test_image_properties_detection_from_source(self):
455455
client._connection = _Connection(RETURNED)
456456

457457
image = client.image(source_uri=IMAGE_SOURCE)
458-
image_properties = image.detect_properties()[0]
458+
image_properties = image.detect_properties()
459459
self.assertIsInstance(image_properties, ImagePropertiesAnnotation)
460460
image_request = client._connection._requested[0]['data']['requests'][0]
461461
self.assertEqual(IMAGE_SOURCE,

0 commit comments

Comments
 (0)
0