Skip to content

Commit 7a52ef3

Browse files
authored
Merge pull request googleapis#2778 from daspecster/vision-add-filename-support
Add loading image from filename.
2 parents 0192cf1 + 3aa5ba2 commit 7a52ef3

File tree

4 files changed

+114
-24
lines changed

4 files changed

+114
-24
lines changed

docs/vision-usage.rst

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
####################
12
Using the Vision API
2-
====================
3+
####################
34

5+
6+
********************************
47
Authentication and Configuration
5-
--------------------------------
8+
********************************
69

710
- For an overview of authentication in ``google-cloud-python``,
811
see :doc:`google-cloud-auth`.
@@ -31,8 +34,43 @@ or pass in ``credentials`` and ``project`` explicitly.
3134
>>> client = vision.Client(project='my-project', credentials=creds)
3235
3336
37+
*****************************************************
38+
Creating an :class:`~google.cloud.vision.image.Image`
39+
*****************************************************
40+
41+
The :class:`~google.cloud.vision.image.Image` class is used to load image
42+
data from sources such as a Google Cloud Storage URI, raw bytes, or a file.
43+
44+
45+
From a Google Cloud Storage URI
46+
===============================
47+
48+
.. code-block:: python
49+
50+
>>> from google.cloud import vision
51+
>>> client = vision.Client()
52+
>>> image = client.image(source_uri='gs://my-test-bucket/image.jpg')
53+
54+
55+
From a filename
56+
===============
57+
58+
.. code-block:: python
59+
60+
>>> image = client.image(filename='image.jpg')
61+
62+
From raw bytes
63+
==============
64+
65+
.. code-block:: python
66+
67+
>>> with open('./image.jpg', 'rb') as image_file:
68+
... bytes_image = client.image(content=image_file.read())
69+
70+
71+
****************
3472
Manual Detection
35-
~~~~~~~~~~~~~~~~
73+
****************
3674

3775
You can call the detection method manually.
3876

@@ -60,8 +98,9 @@ You can call the detection method manually.
6098
'github'
6199
62100
101+
**************
63102
Face Detection
64-
~~~~~~~~~~~~~~
103+
**************
65104

66105
:meth:`~google.cloud.vision.image.Image.detect_faces` will search for faces in
67106
an image and return the coordinates in the image of each `landmark type`_ that
@@ -87,8 +126,9 @@ was detected.
87126
0.02545464
88127
89128
129+
***************
90130
Label Detection
91-
~~~~~~~~~~~~~~~
131+
***************
92132

93133
:meth:`~google.cloud.vision.image.Image.detect_labels` will attempt to label
94134
objects in an image. If there is a car, person and a dog in the image, label
@@ -107,8 +147,9 @@ certainty from ``0.0 to 1.0``.
107147
0.9863683
108148
109149
150+
******************
110151
Landmark Detection
111-
~~~~~~~~~~~~~~~~~~
152+
******************
112153

113154
:meth:`~google.cloud.vision.image.Image.detect_landmarks` will attempt to
114155
detect landmarks such as "Mount Rushmore" and the "Sydney Opera House". The API
@@ -133,8 +174,9 @@ will also provide their known geographical locations if available.
133174
162
134175
135176
177+
**************
136178
Logo Detection
137-
~~~~~~~~~~~~~~
179+
**************
138180

139181
With :meth:`~google.cloud.vision.image.Image.detect_logos`, you can identify
140182
brand logos in an image. Their shape and location in the image can be found by
@@ -162,8 +204,9 @@ iterating through the detected logo's ``vertices``.
162204
62
163205
164206
207+
*********************
165208
Safe Search Detection
166-
~~~~~~~~~~~~~~~~~~~~~
209+
*********************
167210

168211
:meth:`~google.cloud.vision.image.Image.detect_safe_search` will try to
169212
categorize the entire contents of the image under four categories.
@@ -192,8 +235,9 @@ categorize the entire contents of the image under four categories.
192235
'LIKELY'
193236
194237
238+
**************
195239
Text Detection
196-
~~~~~~~~~~~~~~
240+
**************
197241

198242
:meth:`~google.cloud.vision.image.Image.detect_text` performs OCR to find text
199243
in an image.
@@ -213,8 +257,9 @@ in an image.
213257
'some other text in the image'
214258
215259
260+
****************
216261
Image Properties
217-
~~~~~~~~~~~~~~~~
262+
****************
218263

219264
:meth:`~google.cloud.vision.image.Image.detect_properties` will process the
220265
image and determine the dominant colors in the image.
@@ -238,8 +283,9 @@ image and determine the dominant colors in the image.
238283
0.758658
239284
240285
286+
****************
241287
No results found
242-
~~~~~~~~~~~~~~~~
288+
****************
243289

244290
If no results for the detection performed can be extracted from the image, then
245291
an empty list is returned. This behavior is similiar with all detection types.

vision/google/cloud/vision/image.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,35 @@ class Image(object):
3030
:type content: bytes
3131
:param content: Byte stream of an image.
3232
33+
:type filename: str
34+
:param filename: Filename to image.
35+
3336
:type source_uri: str
3437
:param source_uri: Google Cloud Storage URI of image.
3538
3639
:type client: :class:`~google.cloud.vision.client.Client`
3740
:param client: Instance of Vision client.
3841
"""
3942

40-
def __init__(self, client, content=None, source_uri=None):
43+
def __init__(self, client, content=None, filename=None, source_uri=None):
44+
sources = [source for source in (content, filename, source_uri)
45+
if source is not None]
46+
if len(sources) != 1:
47+
raise ValueError(
48+
'Specify exactly one of "content", "filename", or '
49+
'"source_uri".')
50+
4151
self.client = client
42-
self._content = None
43-
self._source = None
4452

45-
if source_uri:
46-
self._source = source_uri
47-
else:
48-
self._content = _bytes_to_unicode(b64encode(_to_bytes(content)))
53+
if filename is not None:
54+
with open(filename, 'rb') as file_obj:
55+
content = file_obj.read()
56+
57+
if content is not None:
58+
content = _bytes_to_unicode(b64encode(_to_bytes(content)))
59+
60+
self._content = content
61+
self._source = source_uri
4962

5063
def as_dict(self):
5164
"""Generate dictionary structure for request.

vision/tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ localdeps =
77
pip install --quiet --upgrade {toxinidir}/../core
88
deps =
99
{toxinidir}/../core
10+
mock
1011
pytest
1112
covercmd =
1213
py.test --quiet \

vision/unit_tests/test_image.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,44 @@ def _get_target_class():
3333
def _make_one(self, *args, **kw):
3434
return self._get_target_class()(*args, **kw)
3535

36+
def test_must_set_one_source(self):
37+
with self.assertRaises(ValueError):
38+
self._make_one(CLIENT_MOCK)
39+
40+
with self.assertRaises(ValueError):
41+
self._make_one(CLIENT_MOCK, content=IMAGE_CONTENT,
42+
source_uri=IMAGE_SOURCE)
43+
44+
with self.assertRaises(ValueError):
45+
self._make_one(CLIENT_MOCK, content=IMAGE_CONTENT,
46+
source_uri=IMAGE_SOURCE, filename='myimage.jpg')
47+
48+
image = self._make_one(CLIENT_MOCK, content=IMAGE_CONTENT)
49+
self.assertEqual(image.content, B64_IMAGE_CONTENT)
50+
3651
def test_image_source_type_content(self):
3752
image = self._make_one(CLIENT_MOCK, content=IMAGE_CONTENT)
3853

39-
_AS_DICT = {
40-
'content': B64_IMAGE_CONTENT
54+
as_dict = {
55+
'content': B64_IMAGE_CONTENT,
4156
}
4257

4358
self.assertEqual(B64_IMAGE_CONTENT, image.content)
4459
self.assertEqual(None, image.source)
45-
self.assertEqual(_AS_DICT, image.as_dict())
60+
self.assertEqual(image.as_dict(), as_dict)
4661

4762
def test_image_source_type_google_cloud_storage(self):
4863
image = self._make_one(CLIENT_MOCK, source_uri=IMAGE_SOURCE)
4964

50-
_AS_DICT = {
65+
as_dict = {
5166
'source': {
52-
'gcs_image_uri': IMAGE_SOURCE
67+
'gcs_image_uri': IMAGE_SOURCE,
5368
}
5469
}
5570

5671
self.assertEqual(IMAGE_SOURCE, image.source)
5772
self.assertEqual(None, image.content)
58-
self.assertEqual(_AS_DICT, image.as_dict())
73+
self.assertEqual(image.as_dict(), as_dict)
5974

6075
def test_cannot_set_both_source_and_content(self):
6176
image = self._make_one(CLIENT_MOCK, content=IMAGE_CONTENT)
@@ -68,3 +83,18 @@ def test_cannot_set_both_source_and_content(self):
6883
self.assertEqual(IMAGE_SOURCE, image.source)
6984
with self.assertRaises(AttributeError):
7085
image.content = IMAGE_CONTENT
86+
87+
def test_image_from_filename(self):
88+
from mock import mock_open
89+
from mock import patch
90+
91+
as_dict = {
92+
'content': B64_IMAGE_CONTENT,
93+
}
94+
95+
with patch('google.cloud.vision.image.open',
96+
mock_open(read_data=IMAGE_CONTENT)) as m:
97+
image = self._make_one(CLIENT_MOCK, filename='my-image-file.jpg')
98+
m.assert_called_once_with('my-image-file.jpg', 'rb')
99+
self.assertEqual(image.content, B64_IMAGE_CONTENT)
100+
self.assertEqual(image.as_dict(), as_dict)

0 commit comments

Comments
 (0)