Skip to content

Commit

Permalink
Add GAPIC support for safe search.
Browse files Browse the repository at this point in the history
  • Loading branch information
daspecster committed Jan 23, 2017
1 parent 6eed70a commit 259711d
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 38 deletions.
3 changes: 1 addition & 2 deletions docs/vision-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,7 @@ categorize the entire contents of the image under four categories.
>>> client = vision.Client()
>>> with open('./image.jpg', 'rb') as image_file:
... image = client.image(content=image_file.read())
>>> safe_search_results = image.detect_safe_search()
>>> safe_search = safe_search_results[0]
>>> safe_search = image.detect_safe_search()
>>> safe_search.adult
<Likelihood.VERY_UNLIKELY: 'VERY_UNLIKELY'>
>>> safe_search.spoof
Expand Down
18 changes: 3 additions & 15 deletions system_tests/vision.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,19 +364,13 @@ def _assert_safe_search(self, safe_search):
self._assert_likelihood(safe_search.violence)

def test_detect_safe_search_content(self):
self._pb_not_implemented_skip(
'gRPC not implemented for safe search detection.')
client = Config.CLIENT
with open(FACE_FILE, 'rb') as image_file:
image = client.image(content=image_file.read())
safe_searches = image.detect_safe_search()
self.assertEqual(len(safe_searches), 1)
safe_search = safe_searches[0]
safe_search = image.detect_safe_search()
self._assert_safe_search(safe_search)

def test_detect_safe_search_gcs(self):
self._pb_not_implemented_skip(
'gRPC not implemented for safe search detection.')
bucket_name = Config.TEST_BUCKET.name
blob_name = 'faces.jpg'
blob = Config.TEST_BUCKET.blob(blob_name)
Expand All @@ -388,19 +382,13 @@ def test_detect_safe_search_gcs(self):

client = Config.CLIENT
image = client.image(source_uri=source_uri)
safe_searches = image.detect_safe_search()
self.assertEqual(len(safe_searches), 1)
safe_search = safe_searches[0]
safe_search = image.detect_safe_search()
self._assert_safe_search(safe_search)

def test_detect_safe_search_filename(self):
self._pb_not_implemented_skip(
'gRPC not implemented for safe search detection.')
client = Config.CLIENT
image = client.image(filename=FACE_FILE)
safe_searches = image.detect_safe_search()
self.assertEqual(len(safe_searches), 1)
safe_search = safe_searches[0]
safe_search = image.detect_safe_search()
self._assert_safe_search(safe_search)


Expand Down
15 changes: 15 additions & 0 deletions vision/google/cloud/vision/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ def _process_image_annotations(image):
'logos': _make_entity_from_pb(image.logo_annotations),
'properties': _make_image_properties_from_pb(
image.image_properties_annotation),
'safe_searches': _make_safe_search_from_pb(
image.safe_search_annotation),
'texts': _make_entity_from_pb(image.text_annotations),
}

Expand Down Expand Up @@ -170,6 +172,19 @@ def _make_image_properties_from_pb(image_properties):
return ImagePropertiesAnnotation.from_pb(image_properties)


def _make_safe_search_from_pb(safe_search):
"""Create ``SafeSearchAnnotation`` object from a protobuf response.
:type safe_search: :class:`~google.cloud.grpc.vision.v1.\
image_annotator_pb2.SafeSearchAnnotation`
:param safe_search: Protobuf instance of ``SafeSearchAnnotation``.
:rtype: :class: `~google.cloud.vision.safe.SafeSearchAnnotation`
:returns: Instance of ``SafeSearchAnnotation``.
"""
return SafeSearchAnnotation.from_pb(safe_search)


def _entity_from_response_type(feature_type, results):
"""Convert a JSON result to an entity type based on the feature.
Expand Down
16 changes: 1 addition & 15 deletions vision/google/cloud/vision/face.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,12 @@

from enum import Enum

from google.cloud.grpc.vision.v1 import image_annotator_pb2

from google.cloud.vision.geometry import BoundsBase
from google.cloud.vision.likelihood import _get_pb_likelihood
from google.cloud.vision.likelihood import Likelihood
from google.cloud.vision.geometry import Position


def _get_pb_likelihood(likelihood):
"""Convert protobuf Likelihood integer value to Likelihood instance.
:type likelihood: int
:param likelihood: Protobuf integer representing ``Likelihood``.
:rtype: :class:`~google.cloud.vision.likelihood.Likelihood`
:returns: Instance of ``Likelihood`` converted from protobuf value.
"""
likelihood_pb = image_annotator_pb2.Likelihood.Name(likelihood)
return Likelihood[likelihood_pb]


class Angles(object):
"""Angles representing the positions of a face."""
def __init__(self, roll, pan, tilt):
Expand Down
15 changes: 15 additions & 0 deletions vision/google/cloud/vision/likelihood.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@

from enum import Enum

from google.cloud.grpc.vision.v1 import image_annotator_pb2


def _get_pb_likelihood(likelihood):
"""Convert protobuf Likelihood integer value to Likelihood enum.
:type likelihood: int
:param likelihood: Protobuf integer representing ``Likelihood``.
:rtype: :class:`~google.cloud.vision.likelihood.Likelihood`
:returns: Enum ``Likelihood`` converted from protobuf value.
"""
likelihood_pb = image_annotator_pb2.Likelihood.Name(likelihood)
return Likelihood[likelihood_pb]


class Likelihood(Enum):
"""A representation of likelihood to give stable results across upgrades.
Expand Down
26 changes: 21 additions & 5 deletions vision/google/cloud/vision/safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Safe search class for information returned from annotating an image."""


from google.cloud.vision.likelihood import _get_pb_likelihood
from google.cloud.vision.likelihood import Likelihood


Expand Down Expand Up @@ -54,14 +54,30 @@ def from_api_repr(cls, response):
:rtype: :class:`~google.cloud.vision.safe.SafeSearchAnnotation`
:returns: Instance of ``SafeSearchAnnotation``.
"""
adult_likelihood = getattr(Likelihood, response['adult'])
spoof_likelihood = getattr(Likelihood, response['spoof'])
medical_likelihood = getattr(Likelihood, response['medical'])
violence_likelihood = getattr(Likelihood, response['violence'])
adult_likelihood = Likelihood[response['adult']]
spoof_likelihood = Likelihood[response['spoof']]
medical_likelihood = Likelihood[response['medical']]
violence_likelihood = Likelihood[response['violence']]

return cls(adult_likelihood, spoof_likelihood, medical_likelihood,
violence_likelihood)

@classmethod
def from_pb(cls, image):
"""Factory: construct SafeSearchAnnotation from Vision API response.
:type image: :class:`~google.cloud.grpc.vision.v1.image_annotator_pb2.\
SafeSearchAnnotation`
:param image: Protobuf response from Vision API with safe search data.
:rtype: :class:`~google.cloud.vision.safe.SafeSearchAnnotation`
:returns: Instance of ``SafeSearchAnnotation``.
"""
classifications = map(_get_pb_likelihood, [image.adult, image.spoof,
image.medical,
image.violence])
return cls(*classifications)

@property
def adult(self):
"""Represents the adult contents likelihood for the image.
Expand Down
11 changes: 10 additions & 1 deletion vision/unit_tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def test_ctor(self):
self.assertEqual(annotations.texts, [True])

def test_from_pb(self):
from google.cloud.vision.likelihood import Likelihood
from google.cloud.vision.safe import SafeSearchAnnotation
from google.cloud.grpc.vision.v1 import image_annotator_pb2

image_response = image_annotator_pb2.AnnotateImageResponse()
Expand All @@ -76,9 +78,16 @@ def test_from_pb(self):
self.assertEqual(annotations.faces, [])
self.assertEqual(annotations.landmarks, [])
self.assertEqual(annotations.texts, [])
self.assertEqual(annotations.safe_searches, ())
self.assertIsNone(annotations.properties)

self.assertIsInstance(annotations.safe_searches, SafeSearchAnnotation)
safe_search = annotations.safe_searches
unknown = Likelihood.UNKNOWN
self.assertEqual(safe_search.adult, unknown)
self.assertEqual(safe_search.spoof, unknown)
self.assertEqual(safe_search.medical, unknown)
self.assertEqual(safe_search.violence, unknown)


class Test__make_entity_from_pb(unittest.TestCase):
def _call_fut(self, annotations):
Expand Down
1 change: 1 addition & 0 deletions vision/unit_tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ def test_safe_search_detection_from_source(self):
image_request = client._connection._requested[0]['data']['requests'][0]
self.assertEqual(IMAGE_SOURCE,
image_request['image']['source']['gcs_image_uri'])

self.assertEqual(safe_search.adult, Likelihood.VERY_UNLIKELY)
self.assertEqual(safe_search.spoof, Likelihood.UNLIKELY)
self.assertEqual(safe_search.medical, Likelihood.POSSIBLE)
Expand Down
70 changes: 70 additions & 0 deletions vision/unit_tests/test_safe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest


class TestSafeSearchAnnotation(unittest.TestCase):
@staticmethod
def _get_target_class():
from google.cloud.vision.safe import SafeSearchAnnotation
return SafeSearchAnnotation

def test_safe_search_annotation(self):
from google.cloud.vision.likelihood import Likelihood
from unit_tests._fixtures import SAFE_SEARCH_DETECTION_RESPONSE

response = SAFE_SEARCH_DETECTION_RESPONSE['responses'][0]
safe_search_response = response['safeSearchAnnotation']

safe_search = self._get_target_class().from_api_repr(
safe_search_response)

self.assertEqual(safe_search.adult, Likelihood.VERY_UNLIKELY)
self.assertEqual(safe_search.spoof, Likelihood.UNLIKELY)
self.assertEqual(safe_search.medical, Likelihood.POSSIBLE)
self.assertEqual(safe_search.violence, Likelihood.VERY_UNLIKELY)

def test_pb_safe_search_annotation(self):
from google.cloud.vision.likelihood import Likelihood
from google.cloud.grpc.vision.v1.image_annotator_pb2 import (
Likelihood as LikelihoodPB)
from google.cloud.grpc.vision.v1 import image_annotator_pb2

possible = LikelihoodPB.Value('POSSIBLE')
possible_name = Likelihood.POSSIBLE
safe_search_annotation = image_annotator_pb2.SafeSearchAnnotation(
adult=possible, spoof=possible, medical=possible, violence=possible
)

safe_search = self._get_target_class().from_pb(safe_search_annotation)

self.assertEqual(safe_search.adult, possible_name)
self.assertEqual(safe_search.spoof, possible_name)
self.assertEqual(safe_search.medical, possible_name)
self.assertEqual(safe_search.violence, possible_name)

def test_empty_pb_safe_search_annotation(self):
from google.cloud.vision.likelihood import Likelihood
from google.cloud.grpc.vision.v1 import image_annotator_pb2

unknown = Likelihood.UNKNOWN
safe_search_annotation = image_annotator_pb2.SafeSearchAnnotation()

safe_search = self._get_target_class().from_pb(safe_search_annotation)

self.assertEqual(safe_search.adult, unknown)
self.assertEqual(safe_search.spoof, unknown)
self.assertEqual(safe_search.medical, unknown)
self.assertEqual(safe_search.violence, unknown)

0 comments on commit 259711d

Please sign in to comment.