Skip to content

Commit 5c6485d

Browse files
committed
Add image.detect() for detecting multiple types.
1 parent 8001d45 commit 5c6485d

File tree

6 files changed

+230
-61
lines changed

6 files changed

+230
-61
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
:caption: Vision
154154

155155
vision-usage
156+
vision-annotations
156157
vision-client
157158
vision-color
158159
vision-entity

docs/vision-annotations.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Vision Annotations
2+
==================
3+
4+
Image Annotations
5+
~~~~~~~~~~~~~~~~~
6+
7+
.. automodule:: google.cloud.vision.annotations
8+
:members:
9+
:undoc-members:
10+
:show-inheritance:

docs/vision-usage.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,25 @@ You can call the detection method manually.
8585
.. code-block:: python
8686
8787
>>> from google.cloud import vision
88-
>>> from google.cloud.vision.image import Feature
89-
>>> from google.cloud.vision.image import FeatureTypes
88+
>>> from google.cloud.vision.feature import Feature
89+
>>> from google.cloud.vision.feature import FeatureTypes
9090
>>> client = vision.Client()
9191
>>> image = client.image(source_uri='gs://my-test-bucket/image.jpg')
9292
>>> features = [Feature(FeatureTypes.FACE_DETECTION, 5),
9393
... Feature(FeatureTypes.LOGO_DETECTION, 3)]
9494
>>> annotations = image.detect(features)
95+
>>> len(annotations.faces)
96+
2
97+
>>> for face in annotations.faces:
98+
... print(face.joy_likelihood)
99+
0.94099093
100+
0.54453093
101+
>>> len(annotations.logos)
102+
2
103+
>>> for logo in annotations.logos:
104+
... print(logo.description)
105+
'google'
106+
'github'
95107
96108
Face Detection
97109
~~~~~~~~~~~~~~
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright 2016 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Annotations management for Vision API responses."""
16+
17+
18+
from google.cloud.vision.entity import EntityAnnotation
19+
from google.cloud.vision.face import Face
20+
from google.cloud.vision.feature import FeatureTypes
21+
from google.cloud.vision.color import ImagePropertiesAnnotation
22+
from google.cloud.vision.safe import SafeSearchAnnotation
23+
24+
25+
_REVERSE_TYPES = {
26+
FeatureTypes.FACE_DETECTION: 'faceAnnotations',
27+
FeatureTypes.IMAGE_PROPERTIES: 'imagePropertiesAnnotation',
28+
FeatureTypes.LABEL_DETECTION: 'labelAnnotations',
29+
FeatureTypes.LANDMARK_DETECTION: 'landmarkAnnotations',
30+
FeatureTypes.LOGO_DETECTION: 'logoAnnotations',
31+
FeatureTypes.SAFE_SEARCH_DETECTION: 'safeSearchAnnotation',
32+
FeatureTypes.TEXT_DETECTION: 'textAnnotations',
33+
}
34+
35+
36+
class Annotations(object):
37+
"""Annotation class for managing responses.
38+
39+
:type faces: list
40+
:param faces: List of :class:`~google.cloud.vision.face.Face`.
41+
42+
:type properties: list
43+
:param properties:
44+
List of :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
45+
46+
:type labels: list
47+
:param labels: List of
48+
:class:`~google.cloud.vision.entity.EntityAnnotation`.
49+
50+
:type landmarks: list
51+
:param landmarks: List of
52+
:class:`~google.cloud.vision.entity.EntityAnnotation.`
53+
54+
:type logos: list
55+
:param logos: List of
56+
:class:`~google.cloud.vision.entity.EntityAnnotation`.
57+
58+
:type safe_searches: list
59+
:param safe_searches:
60+
List of :class:`~google.cloud.vision.safe.SafeSearchAnnotation`
61+
62+
:type texts: list
63+
:param texts: List of
64+
:class:`~google.cloud.vision.entity.EntityAnnotation`.
65+
"""
66+
def __init__(self, faces=None, properties=None, labels=None,
67+
landmarks=None, logos=None, safe_searches=None, texts=None):
68+
self.faces = faces or []
69+
self.properties = properties or []
70+
self.labels = labels or []
71+
self.landmarks = landmarks or []
72+
self.logos = logos or []
73+
self.safe_searches = safe_searches or []
74+
self.texts = texts or []
75+
76+
@classmethod
77+
def from_api_repr(cls, response):
78+
"""Factory: construct an instance of ``Annotations`` from a response.
79+
80+
:type response: dict
81+
:param response: Vision API response object.
82+
83+
:rtype: :class:`~google.cloud.vision.annotations.Annotations`
84+
:returns: An instance of ``Annotations`` with detection types loaded.
85+
"""
86+
annotations = {}
87+
88+
for feature_type in response.keys():
89+
annotations[feature_type] = []
90+
91+
for feature_type, annotation in response.iteritems():
92+
annotations[feature_type].extend(
93+
_entity_from_response_type(feature_type, annotation))
94+
95+
faces = annotations.get(
96+
_REVERSE_TYPES[FeatureTypes.FACE_DETECTION], [])
97+
properties = annotations.get(
98+
_REVERSE_TYPES[FeatureTypes.IMAGE_PROPERTIES], [])
99+
labels = annotations.get(
100+
_REVERSE_TYPES[FeatureTypes.LABEL_DETECTION], [])
101+
landmarks = annotations.get(
102+
_REVERSE_TYPES[FeatureTypes.LANDMARK_DETECTION], [])
103+
logos = annotations.get(
104+
_REVERSE_TYPES[FeatureTypes.LOGO_DETECTION], [])
105+
safe_searches = annotations.get(
106+
_REVERSE_TYPES[FeatureTypes.SAFE_SEARCH_DETECTION], [])
107+
texts = annotations.get(
108+
_REVERSE_TYPES[FeatureTypes.TEXT_DETECTION], [])
109+
110+
return cls(faces=faces, properties=properties, labels=labels,
111+
landmarks=landmarks, logos=logos,
112+
safe_searches=safe_searches, texts=texts)
113+
114+
115+
def _entity_from_response_type(feature_type, results):
116+
"""Convert a JSON result to an entity type based on the feature."""
117+
118+
detected_objects = []
119+
if feature_type == _REVERSE_TYPES[FeatureTypes.FACE_DETECTION]:
120+
detected_objects.extend(
121+
Face.from_api_repr(face) for face in results)
122+
elif feature_type == _REVERSE_TYPES[FeatureTypes.IMAGE_PROPERTIES]:
123+
detected_objects.append(
124+
ImagePropertiesAnnotation.from_api_repr(results))
125+
elif feature_type == _REVERSE_TYPES[FeatureTypes.SAFE_SEARCH_DETECTION]:
126+
detected_objects.append(SafeSearchAnnotation.from_api_repr(results))
127+
else:
128+
for result in results:
129+
detected_objects.append(EntityAnnotation.from_api_repr(result))
130+
return detected_objects

vision/google/cloud/vision/image.py

Lines changed: 30 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,9 @@
1919

2020
from google.cloud._helpers import _to_bytes
2121
from google.cloud._helpers import _bytes_to_unicode
22-
from google.cloud.vision.entity import EntityAnnotation
23-
from google.cloud.vision.face import Face
22+
from google.cloud.vision.annotations import Annotations
2423
from google.cloud.vision.feature import Feature
2524
from google.cloud.vision.feature import FeatureTypes
26-
from google.cloud.vision.color import ImagePropertiesAnnotation
27-
from google.cloud.vision.safe import SafeSearchAnnotation
28-
29-
30-
_FACE_DETECTION = 'FACE_DETECTION'
31-
_IMAGE_PROPERTIES = 'IMAGE_PROPERTIES'
32-
_LABEL_DETECTION = 'LABEL_DETECTION'
33-
_LANDMARK_DETECTION = 'LANDMARK_DETECTION'
34-
_LOGO_DETECTION = 'LOGO_DETECTION'
35-
_SAFE_SEARCH_DETECTION = 'SAFE_SEARCH_DETECTION'
36-
_TEXT_DETECTION = 'TEXT_DETECTION'
37-
38-
_REVERSE_TYPES = {
39-
_FACE_DETECTION: 'faceAnnotations',
40-
_IMAGE_PROPERTIES: 'imagePropertiesAnnotation',
41-
_LABEL_DETECTION: 'labelAnnotations',
42-
_LANDMARK_DETECTION: 'landmarkAnnotations',
43-
_LOGO_DETECTION: 'logoAnnotations',
44-
_SAFE_SEARCH_DETECTION: 'safeSearchAnnotation',
45-
_TEXT_DETECTION: 'textAnnotations',
46-
}
4725

4826

4927
class Image(object):
@@ -105,7 +83,7 @@ def source(self):
10583
return self._source
10684

10785
def _detect_annotation(self, features):
108-
"""Generic method for detecting a single annotation.
86+
"""Generic method for detecting annotations.
10987
11088
:type features: list
11189
:param features: List of :class:`~google.cloud.vision.feature.Feature`
@@ -118,12 +96,21 @@ def _detect_annotation(self, features):
11896
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`,
11997
:class:`~google.cloud.vision.sage.SafeSearchAnnotation`,
12098
"""
121-
detected_objects = []
12299
results = self.client.annotate(self, features)
123-
for feature in features:
124-
detected_objects.extend(
125-
_entity_from_response_type(feature.feature_type, results))
126-
return detected_objects
100+
return Annotations.from_api_repr(results)
101+
102+
def detect(self, features):
103+
"""Detect multiple feature types.
104+
105+
:type features: list of :class:`~google.cloud.vision.feature.Feature`
106+
:param features: List of the ``Feature`` indication the type of
107+
annotation to perform.
108+
109+
:rtype: list
110+
:returns: List of
111+
:class:`~google.cloud.vision.entity.EntityAnnotation`.
112+
"""
113+
return self._detect_annotation(features)
127114

128115
def detect_faces(self, limit=10):
129116
"""Detect faces in image.
@@ -135,7 +122,8 @@ def detect_faces(self, limit=10):
135122
:returns: List of :class:`~google.cloud.vision.face.Face`.
136123
"""
137124
features = [Feature(FeatureTypes.FACE_DETECTION, limit)]
138-
return self._detect_annotation(features)
125+
annotations = self._detect_annotation(features)
126+
return annotations.faces
139127

140128
def detect_labels(self, limit=10):
141129
"""Detect labels that describe objects in an image.
@@ -147,7 +135,8 @@ def detect_labels(self, limit=10):
147135
:returns: List of :class:`~google.cloud.vision.entity.EntityAnnotation`
148136
"""
149137
features = [Feature(FeatureTypes.LABEL_DETECTION, limit)]
150-
return self._detect_annotation(features)
138+
annotations = self._detect_annotation(features)
139+
return annotations.labels
151140

152141
def detect_landmarks(self, limit=10):
153142
"""Detect landmarks in an image.
@@ -160,7 +149,8 @@ def detect_landmarks(self, limit=10):
160149
:class:`~google.cloud.vision.entity.EntityAnnotation`.
161150
"""
162151
features = [Feature(FeatureTypes.LANDMARK_DETECTION, limit)]
163-
return self._detect_annotation(features)
152+
annotations = self._detect_annotation(features)
153+
return annotations.landmarks
164154

165155
def detect_logos(self, limit=10):
166156
"""Detect logos in an image.
@@ -173,7 +163,8 @@ def detect_logos(self, limit=10):
173163
:class:`~google.cloud.vision.entity.EntityAnnotation`.
174164
"""
175165
features = [Feature(FeatureTypes.LOGO_DETECTION, limit)]
176-
return self._detect_annotation(features)
166+
annotations = self._detect_annotation(features)
167+
return annotations.logos
177168

178169
def detect_properties(self, limit=10):
179170
"""Detect the color properties of an image.
@@ -186,7 +177,8 @@ def detect_properties(self, limit=10):
186177
:class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
187178
"""
188179
features = [Feature(FeatureTypes.IMAGE_PROPERTIES, limit)]
189-
return self._detect_annotation(features)
180+
annotations = self._detect_annotation(features)
181+
return annotations.properties
190182

191183
def detect_safe_search(self, limit=10):
192184
"""Retreive safe search properties from an image.
@@ -199,7 +191,8 @@ def detect_safe_search(self, limit=10):
199191
:class:`~google.cloud.vision.sage.SafeSearchAnnotation`.
200192
"""
201193
features = [Feature(FeatureTypes.SAFE_SEARCH_DETECTION, limit)]
202-
return self._detect_annotation(features)
194+
annotations = self._detect_annotation(features)
195+
return annotations.safe_searches
203196

204197
def detect_text(self, limit=10):
205198
"""Detect text in an image.
@@ -212,27 +205,5 @@ def detect_text(self, limit=10):
212205
:class:`~google.cloud.vision.entity.EntityAnnotation`.
213206
"""
214207
features = [Feature(FeatureTypes.TEXT_DETECTION, limit)]
215-
return self._detect_annotation(features)
216-
217-
218-
def _entity_from_response_type(feature_type, results):
219-
"""Convert a JSON result to an entity type based on the feature."""
220-
feature_key = _REVERSE_TYPES[feature_type]
221-
annotations = results.get(feature_key, ())
222-
if not annotations:
223-
return []
224-
225-
detected_objects = []
226-
if feature_type == _FACE_DETECTION:
227-
detected_objects.extend(
228-
Face.from_api_repr(face) for face in annotations)
229-
elif feature_type == _IMAGE_PROPERTIES:
230-
detected_objects.append(
231-
ImagePropertiesAnnotation.from_api_repr(annotations))
232-
elif feature_type == _SAFE_SEARCH_DETECTION:
233-
detected_objects.append(
234-
SafeSearchAnnotation.from_api_repr(annotations))
235-
else:
236-
for result in annotations:
237-
detected_objects.append(EntityAnnotation.from_api_repr(result))
238-
return detected_objects
208+
annotations = self._detect_annotation(features)
209+
return annotations.texts

vision/unit_tests/test_client.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,51 @@ def test_image_with_client(self):
8181
image = client.image(source_uri=IMAGE_SOURCE)
8282
self.assertIsInstance(image, Image)
8383

84+
def test_multiple_detection_from_content(self):
85+
from google.cloud.vision.feature import Feature
86+
from google.cloud.vision.feature import FeatureTypes
87+
from unit_tests._fixtures import LABEL_DETECTION_RESPONSE
88+
from unit_tests._fixtures import LOGO_DETECTION_RESPONSE
89+
RETURNED = LABEL_DETECTION_RESPONSE
90+
LOGOS = LOGO_DETECTION_RESPONSE['responses'][0]['logoAnnotations']
91+
RETURNED['responses'][0]['logoAnnotations'] = LOGOS
92+
93+
credentials = _Credentials()
94+
client = self._make_one(project=PROJECT, credentials=credentials)
95+
client._connection = _Connection(RETURNED)
96+
97+
limit = 2
98+
label_feature = Feature(FeatureTypes.LABEL_DETECTION, limit)
99+
logo_feature = Feature(FeatureTypes.LOGO_DETECTION, limit)
100+
features = [label_feature, logo_feature]
101+
image = client.image(content=IMAGE_CONTENT)
102+
items = image.detect(features)
103+
104+
self.assertEqual(len(items.logos), 2)
105+
self.assertEqual(len(items.labels), 3)
106+
self.assertEqual(items.logos[0].description, 'Brand1')
107+
self.assertEqual(items.logos[0].score, 0.63192177)
108+
self.assertEqual(items.logos[1].description, 'Brand2')
109+
self.assertEqual(items.logos[1].score, 0.5492993)
110+
111+
self.assertEqual(items.labels[0].description, 'automobile')
112+
self.assertEqual(items.labels[0].score, 0.9776855)
113+
self.assertEqual(items.labels[1].description, 'vehicle')
114+
self.assertEqual(items.labels[1].score, 0.947987)
115+
self.assertEqual(items.labels[2].description, 'truck')
116+
self.assertEqual(items.labels[2].score, 0.88429511)
117+
118+
image_request = client._connection._requested[0]['data']['requests'][0]
119+
label_request = image_request['features'][0]
120+
logo_request = image_request['features'][1]
121+
122+
self.assertEqual(B64_IMAGE_CONTENT,
123+
image_request['image']['content'])
124+
self.assertEqual(label_request['maxResults'], 2)
125+
self.assertEqual(label_request['type'], 'LABEL_DETECTION')
126+
self.assertEqual(logo_request['maxResults'], 2)
127+
self.assertEqual(logo_request['type'], 'LOGO_DETECTION')
128+
84129
def test_face_detection_from_source(self):
85130
from google.cloud.vision.face import Face
86131
from unit_tests._fixtures import FACE_DETECTION_RESPONSE

0 commit comments

Comments
 (0)