Skip to content

Commit 83a0b98

Browse files
Vision 1.1 API Client (#3069)
1 parent 74b8622 commit 83a0b98

File tree

18 files changed

+1463
-30
lines changed

18 files changed

+1463
-30
lines changed

packages/google-cloud-vision/google/cloud/vision/_gax.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __init__(self, client=None):
3333
credentials=client._credentials, lib_name='gccl',
3434
lib_version=__version__)
3535

36-
def annotate(self, images):
36+
def annotate(self, images=None, requests_pb=None):
3737
"""Annotate images through GAX.
3838
3939
:type images: list
@@ -42,18 +42,28 @@ def annotate(self, images):
4242
:class:`~google.cloud.vision.feature.Feature`.
4343
e.g. [(image, [feature_one, feature_two]),]
4444
45+
:type requests_pb: list
46+
:param requests_pb: List of :class:`google.cloud.proto.vision.v1.\
47+
image_annotator_pb2.AnnotateImageRequest`
48+
4549
:rtype: list
4650
:returns: List of
4751
:class:`~google.cloud.vision.annotations.Annotations`.
4852
"""
49-
requests = []
50-
for image, features in images:
51-
gapic_features = [_to_gapic_feature(feature)
52-
for feature in features]
53-
gapic_image = _to_gapic_image(image)
54-
request = image_annotator_pb2.AnnotateImageRequest(
55-
image=gapic_image, features=gapic_features)
56-
requests.append(request)
53+
if any([images, requests_pb]) is False:
54+
return []
55+
56+
if requests_pb is None:
57+
requests = []
58+
for image, features in images:
59+
gapic_features = [_to_gapic_feature(feature)
60+
for feature in features]
61+
gapic_image = _to_gapic_image(image)
62+
request = image_annotator_pb2.AnnotateImageRequest(
63+
image=gapic_image, features=gapic_features)
64+
requests.append(request)
65+
else:
66+
requests = requests_pb
5767

5868
annotator_client = self._annotator_client
5969
responses = annotator_client.batch_annotate_images(requests).responses
@@ -89,9 +99,16 @@ def _to_gapic_image(image):
8999
if image.content is not None:
90100
return image_annotator_pb2.Image(content=image.content)
91101
if image.source is not None:
92-
return image_annotator_pb2.Image(
93-
source=image_annotator_pb2.ImageSource(
94-
gcs_image_uri=image.source
95-
),
96-
)
102+
if image.source.startswith('gs://'):
103+
return image_annotator_pb2.Image(
104+
source=image_annotator_pb2.ImageSource(
105+
gcs_image_uri=image.source
106+
),
107+
)
108+
elif image.source.startswith(('http://', 'https://')):
109+
return image_annotator_pb2.Image(
110+
source=image_annotator_pb2.ImageSource(
111+
image_uri=image.source
112+
),
113+
)
97114
raise ValueError('No image content or source found.')

packages/google-cloud-vision/google/cloud/vision/_http.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414

1515
"""HTTP Client for interacting with the Google Cloud Vision API."""
1616

17+
import json
1718

1819
from google.cloud import _http
1920

2021
from google.cloud.vision import __version__
2122
from google.cloud.vision.annotations import Annotations
2223
from google.cloud.vision.feature import Feature
2324

25+
from google.protobuf import json_format
26+
2427

2528
_CLIENT_INFO = _http.CLIENT_INFO_TEMPLATE.format(__version__)
2629

@@ -57,19 +60,35 @@ def __init__(self, client):
5760
self._client = client
5861
self._connection = Connection(client)
5962

60-
def annotate(self, images):
63+
def annotate(self, images=None, requests_pb=None):
6164
"""Annotate an image to discover it's attributes.
6265
6366
:type images: list of :class:`~google.cloud.vision.image.Image`
6467
:param images: A list of ``Image``.
6568
69+
:rtype: list
70+
:returns: List of :class:`~googe.cloud.vision.annotations.Annotations`.
71+
72+
:type requests_pb: list
73+
:param requests_pb: List of :class:`google.cloud.proto.vision.v1.\
74+
image_annotator_b2.AnnotateImageRequest`.
75+
6676
:rtype: list
6777
:returns: List of :class:`~googe.cloud.vision.annotations.Annotations`.
6878
"""
79+
if any([images, requests_pb]) is False:
80+
return []
81+
6982
requests = []
70-
for image, features in images:
71-
requests.append(_make_request(image, features))
83+
if requests_pb is None:
84+
for image, features in images:
85+
requests.append(_make_request(image, features))
86+
else:
87+
requests = [json.loads(json_format.MessageToJson(request))
88+
for request in requests_pb]
89+
7290
data = {'requests': requests}
91+
7392
api_response = self._connection.api_request(
7493
method='POST', path='/images:annotate', data=data)
7594
responses = api_response.get('responses')

packages/google-cloud-vision/google/cloud/vision/annotations.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,55 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
# pylint: disable=too-many-arguments
1516
"""Annotations management for Vision API responses."""
1617

1718
import six
1819

1920
from google.cloud.vision.color import ImagePropertiesAnnotation
21+
from google.cloud.vision.crop_hint import CropHint
2022
from google.cloud.vision.entity import EntityAnnotation
2123
from google.cloud.vision.face import Face
2224
from google.cloud.vision.safe_search import SafeSearchAnnotation
25+
from google.cloud.vision.text import TextAnnotation
26+
from google.cloud.vision.web import WebDetection
2327

2428

29+
_CROP_HINTS_ANNOTATION = 'cropHintsAnnotation'
2530
_FACE_ANNOTATIONS = 'faceAnnotations'
31+
_FULL_TEXT_ANNOTATION = 'fullTextAnnotation'
2632
_IMAGE_PROPERTIES_ANNOTATION = 'imagePropertiesAnnotation'
2733
_SAFE_SEARCH_ANNOTATION = 'safeSearchAnnotation'
34+
_WEB_ANNOTATION = 'webDetection'
2835

2936
_KEY_MAP = {
37+
_CROP_HINTS_ANNOTATION: 'crop_hints',
3038
_FACE_ANNOTATIONS: 'faces',
39+
_FULL_TEXT_ANNOTATION: 'full_texts',
3140
_IMAGE_PROPERTIES_ANNOTATION: 'properties',
3241
'labelAnnotations': 'labels',
3342
'landmarkAnnotations': 'landmarks',
3443
'logoAnnotations': 'logos',
3544
_SAFE_SEARCH_ANNOTATION: 'safe_searches',
36-
'textAnnotations': 'texts'
45+
'textAnnotations': 'texts',
46+
_WEB_ANNOTATION: 'web',
3747
}
3848

3949

4050
class Annotations(object):
4151
"""Helper class to bundle annotation responses.
4252
53+
:type crop_hints: list
54+
:param crop_hints: List of
55+
:class:`~google.cloud.vision.crop_hint.CropHintsAnnotation`.
56+
4357
:type faces: list
4458
:param faces: List of :class:`~google.cloud.vision.face.Face`.
4559
60+
:type full_texts: list
61+
:param full_texts: List of
62+
:class:`~google.cloud.vision.text.TextAnnotation`.
63+
4664
:type properties: list
4765
:param properties:
4866
List of :class:`~google.cloud.vision.color.ImagePropertiesAnnotation`.
@@ -66,16 +84,23 @@ class Annotations(object):
6684
:type texts: list
6785
:param texts: List of
6886
:class:`~google.cloud.vision.entity.EntityAnnotation`.
87+
88+
:type web: list
89+
:param web: List of :class:`~google.cloud.vision.web.WebDetection`.
6990
"""
70-
def __init__(self, faces=(), properties=(), labels=(), landmarks=(),
71-
logos=(), safe_searches=(), texts=()):
91+
def __init__(self, crop_hints=(), faces=(), full_texts=(), properties=(),
92+
labels=(), landmarks=(), logos=(), safe_searches=(),
93+
texts=(), web=()):
94+
self.crop_hints = crop_hints
7295
self.faces = faces
96+
self.full_texts = full_texts
7397
self.properties = properties
7498
self.labels = labels
7599
self.landmarks = landmarks
76100
self.logos = logos
77101
self.safe_searches = safe_searches
78102
self.texts = texts
103+
self.web = web
79104

80105
@classmethod
81106
def from_api_repr(cls, response):
@@ -121,7 +146,9 @@ def _process_image_annotations(image):
121146
:returns: Dictionary populated with entities from response.
122147
"""
123148
return {
149+
'crop_hints': _make_crop_hints_from_pb(image.crop_hints_annotation),
124150
'faces': _make_faces_from_pb(image.face_annotations),
151+
'full_texts': _make_full_text_from_pb(image.full_text_annotation),
125152
'labels': _make_entity_from_pb(image.label_annotations),
126153
'landmarks': _make_entity_from_pb(image.landmark_annotations),
127154
'logos': _make_entity_from_pb(image.logo_annotations),
@@ -130,9 +157,24 @@ def _process_image_annotations(image):
130157
'safe_searches': _make_safe_search_from_pb(
131158
image.safe_search_annotation),
132159
'texts': _make_entity_from_pb(image.text_annotations),
160+
'web': _make_web_detection_from_pb(image.web_detection)
133161
}
134162

135163

164+
def _make_crop_hints_from_pb(crop_hints):
165+
"""Create list of ``CropHint`` objects from a protobuf response.
166+
167+
:type crop_hints: list
168+
:param crop_hints: List of
169+
:class:`google.cloud.grpc.vision.v1.\
170+
image_annotator_pb2.CropHintsAnnotation`
171+
172+
:rtype: list
173+
:returns: List of ``CropHint`` objects.
174+
"""
175+
return [CropHint.from_pb(hint) for hint in crop_hints.crop_hints]
176+
177+
136178
def _make_entity_from_pb(annotations):
137179
"""Create an entity from a protobuf response.
138180
@@ -159,6 +201,19 @@ def _make_faces_from_pb(faces):
159201
return [Face.from_pb(face) for face in faces]
160202

161203

204+
def _make_full_text_from_pb(full_text):
205+
"""Create text annotation object from protobuf response.
206+
207+
:type full_text: :class:`~google.cloud.proto.vision.v1.\
208+
text_annotation_pb2.TextAnnotation`
209+
:param full_text: Protobuf instance of ``TextAnnotation``.
210+
211+
:rtype: :class:`~google.cloud.vision.text.TextAnnotation`
212+
:returns: Instance of ``TextAnnotation``.
213+
"""
214+
return TextAnnotation.from_pb(full_text)
215+
216+
162217
def _make_image_properties_from_pb(image_properties):
163218
"""Create ``ImageProperties`` object from a protobuf response.
164219
@@ -186,6 +241,19 @@ def _make_safe_search_from_pb(safe_search):
186241
return SafeSearchAnnotation.from_pb(safe_search)
187242

188243

244+
def _make_web_detection_from_pb(annotation):
245+
"""Create ``WebDetection`` object from a protobuf response.
246+
247+
:type annotation: :class:`~google.cloud.proto.vision.v1.web_detection_pb2\
248+
.WebDetection`
249+
:param annotation: Protobuf instance of ``WebDetection``.
250+
251+
:rtype: :class: `~google.cloud.vision.web.WebDetection`
252+
:returns: Instance of ``WebDetection``.
253+
"""
254+
return WebDetection.from_pb(annotation)
255+
256+
189257
def _entity_from_response_type(feature_type, results):
190258
"""Convert a JSON result to an entity type based on the feature.
191259
@@ -207,6 +275,14 @@ def _entity_from_response_type(feature_type, results):
207275
return ImagePropertiesAnnotation.from_api_repr(results)
208276
elif feature_type == _SAFE_SEARCH_ANNOTATION:
209277
return SafeSearchAnnotation.from_api_repr(results)
278+
elif feature_type == _WEB_ANNOTATION:
279+
return WebDetection.from_api_repr(results)
280+
elif feature_type == _CROP_HINTS_ANNOTATION:
281+
crop_hints = results.get('cropHints', [])
282+
detected_objects.extend(
283+
CropHint.from_api_repr(result) for result in crop_hints)
284+
elif feature_type == _FULL_TEXT_ANNOTATION:
285+
return TextAnnotation.from_api_repr(results)
210286
else:
211287
for result in results:
212288
detected_objects.append(EntityAnnotation.from_api_repr(result))

packages/google-cloud-vision/google/cloud/vision/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def image(self, content=None, filename=None, source_uri=None):
8888
:param filename: Filename to image.
8989
9090
:type source_uri: str
91-
:param source_uri: Google Cloud Storage URI of image.
91+
:param source_uri: URL or Google Cloud Storage URI of image.
9292
9393
:rtype: :class:`~google.cloud.vision.image.Image`
9494
:returns: Image instance with the current client attached.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2017 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+
"""Representation of Vision API's crop hints."""
16+
17+
from google.cloud.vision.geometry import Bounds
18+
19+
20+
class CropHint(object):
21+
"""Representation of a crop hint returned from the Vision API.
22+
23+
:type bounds: dict
24+
:param bounds: Dictionary of boundary information of detected entity.
25+
26+
:type confidence: float
27+
:param confidence: Confidence of this being a salient region.
28+
29+
:type importance_fraction: float
30+
:param importance_fraction: Fraction of importance of this region.
31+
"""
32+
def __init__(self, bounds, confidence, importance_fraction):
33+
self._bounds = bounds
34+
self._confidence = confidence
35+
self._importance_fraction = importance_fraction
36+
37+
@classmethod
38+
def from_api_repr(cls, response):
39+
"""Factory: construct ``CropHint`` from Vision API response.
40+
41+
:type response: dict
42+
:param response: Dictionary response from Vision API with entity data.
43+
44+
:rtype: :class:`~google.cloud.vision.crop_hint.CropHint`
45+
:returns: Instance of ``CropHint``.
46+
"""
47+
bounds = Bounds.from_api_repr(response.get('boundingPoly'))
48+
confidence = response.get('confidence', 0.0)
49+
importance_fraction = response.get('importanceFraction', 0.0)
50+
return cls(bounds, confidence, importance_fraction)
51+
52+
@classmethod
53+
def from_pb(cls, response):
54+
"""Factory: construct ``CropHint`` from Vision gRPC response.
55+
56+
:type response: :class:`google.cloud.proto.vision.v1.\
57+
image_annotator_pb2.CropHint`
58+
:param response: gRPC response from Vision API with entity data.
59+
60+
:rtype: :class:`~google.cloud.vision.crop_hint.CropHint`
61+
:returns: Instance of ``CropHint``.
62+
"""
63+
bounds = Bounds.from_pb(response.bounding_poly)
64+
return cls(bounds, response.confidence, response.importance_fraction)
65+
66+
@property
67+
def bounds(self):
68+
"""Bounding polygon of crop hints.
69+
70+
:rtype: :class:`~google.cloud.vision.geometry.Bounds`
71+
:returns: Instance of ``Bounds`` with populated vertices.
72+
"""
73+
return self._bounds
74+
75+
@property
76+
def confidence(self):
77+
"""Confidence of this being a salient region. Range [0, 1].
78+
79+
:rtype: float
80+
:returns: float between 0 and 1, inclusive.
81+
"""
82+
return self._confidence
83+
84+
@property
85+
def importance_fraction(self):
86+
"""Fraction of importance of this salient region with respect to the
87+
original image.
88+
89+
:rtype: float
90+
:returns: float
91+
"""
92+
return self._importance_fraction

0 commit comments

Comments
 (0)