Skip to content

Commit

Permalink
Add GCF imagemagick samples (#1684)
Browse files Browse the repository at this point in the history
* Initial commit of imagemagick sample

Change-Id: Ie8aca88b0dc80926438c9e87b2e71071f8f294d6

* Add unit tests + get them passing

Change-Id: I75ec970fd22c870664e4133f51562793a464df1f

* Get sample working on GCF

Change-Id: I255301ab7218b591c6c61f7da059a3d577bd3186

* Address comments, pt 1

Change-Id: I31808597e16b7b2201adbd8a51fe84241c90e649

* Address comments, pt 2

Change-Id: I465cb4531a024265f66f857cbd793949b597d252

* Use UserDict instead of DictObject

Change-Id: Ie69ce88686481640da59207682b078b800631620

* Address comments

Change-Id: I62036d693fcf2451fbd1129d6799a736852ea455

* Use format strings + make function idempotent

Change-Id: Iaddf913fd9be99e7daff6caf45aac2a03c1dceeb
  • Loading branch information
Ace Nassri authored Sep 27, 2018
1 parent 9bacd2e commit 06cc18c
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
47 changes: 47 additions & 0 deletions functions/imagemagick/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<img src="https://avatars2.githubusercontent.com/u/2810941?v=3&s=96" alt="Google Cloud Platform logo" title="Google Cloud Platform" align="right" height="96" width="96"/>

# Google Cloud Functions ImageMagick sample

This sample shows you how to blur an image using ImageMagick in a
Storage-triggered Cloud Function.

View the [source code][code].

[code]: main.py

## Deploy and Test

1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud
Functions for your project.

1. Clone this repository:

git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
cd python-docs-samples/functions/imagemagick

1. Create a Cloud Storage Bucket:

gsutil mb gs://YOUR_BUCKET_NAME

This storage bucket is used to upload images for the function to check.

1. Deploy the `blur_offensive_images` function with a Storage trigger:

gcloud functions deploy blur_offensive_images --trigger-bucket=YOUR_BUCKET_NAME --runtime python37

* Replace `YOUR_BUCKET_NAME` with the name of the Cloud Storage Bucket you created earlier.

1. Upload an offensive image to the Storage bucket, such as this image of
a flesh-eating zombie: https://cdn.pixabay.com/photo/2015/09/21/14/24/zombie-949916_1280.jpg

1. Check the logs for the `blur_offensive_images` function:

gcloud functions get-logs blur_offensive_images

You should see something like this in your console:

D ... User function triggered, starting execution
I ... `The image zombie.jpg has been detected as inappropriate.`
D ... Execution took 1 ms, user function completed successfully

[quickstart]: https://cloud.google.com/functions/quickstart
85 changes: 85 additions & 0 deletions functions/imagemagick/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2018 Google LLC
#
# 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.


# [START functions_imagemagick_setup]
import os
import tempfile

from google.cloud import storage, vision
from wand.image import Image

storage_client = storage.Client()
vision_client = vision.ImageAnnotatorClient()
# [END functions_imagemagick_setup]


# [START functions_imagemagick_analyze]
# Blurs uploaded images that are flagged as Adult or Violence.
def blur_offensive_images(data, context):
file_data = data

file_name = file_data['name']
bucket_name = file_data['bucket']

blob = storage_client.bucket(bucket_name).get_blob(file_name)
blob_uri = f'gs://{bucket_name}/{file_name}'
blob_source = {'source': {'image_uri': blob_uri}}

# Ignore already-blurred files
if file_name.startswith('blurred-'):
print(f'The image {file_name} is already blurred.')
return

print(f'Analyzing {file_name}.')

result = vision_client.safe_search_detection(blob_source)
detected = result.safe_search_annotation

# Process image
if detected.adult == 5 or detected.violence == 5:
print(f'The image {file_name} was detected as inappropriate.')
return __blur_image(blob)
else:
print(f'The image {file_name} was detected as OK.')
# [END functions_imagemagick_analyze]


# [START functions_imagemagick_blur]
# Blurs the given file using ImageMagick.
def __blur_image(current_blob):
file_name = current_blob.name
_, temp_local_filename = tempfile.mkstemp()

# Download file from bucket.
current_blob.download_to_filename(temp_local_filename)
print(f'Image {file_name} was downloaded to {temp_local_filename}.')

# Blur the image using ImageMagick.
with Image(filename=temp_local_filename) as image:
image.resize(*image.size, blur=16, filter='hamming')
image.save(filename=temp_local_filename)

print(f'Image {file_name} was blurred.')

# Send Blurred image back to the bucket (with a 'blurred-' prefix).
# The prefix is necessary to avoid re-invoking the function upon upload.
new_file_name = f'blurred-{file_name}'
new_blob = current_blob.bucket.blob(new_file_name)
new_blob.upload_from_filename(temp_local_filename)
print(f'Blurred image was uploaded to {new_file_name}.')

# Delete the temporary file.
os.remove(temp_local_filename)
# [END functions_imagemagick_blur]
107 changes: 107 additions & 0 deletions functions/imagemagick/main_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2018 Google LLC
#
# 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.

from collections import UserDict
import uuid

from mock import MagicMock, patch

import main


@patch('main.__blur_image')
@patch('main.vision_client')
@patch('main.storage_client')
def test_process_offensive_image(
storage_client,
vision_client,
__blur_image,
capsys):
result = UserDict()
result.safe_search_annotation = UserDict()
result.safe_search_annotation.adult = 5
result.safe_search_annotation.violence = 5
vision_client.safe_search_detection = MagicMock(return_value=result)

filename = str(uuid.uuid4())
data = {
'bucket': 'my-bucket',
'name': filename
}

main.blur_offensive_images(data, None)

out, _ = capsys.readouterr()
assert 'Analyzing %s.' % filename in out
assert 'The image %s was detected as inappropriate.' % filename in out
assert main.__blur_image.called


@patch('main.__blur_image')
@patch('main.vision_client')
@patch('main.storage_client')
def test_process_safe_image(
storage_client,
vision_client,
__blur_image,
capsys):
result = UserDict()
result.safe_search_annotation = UserDict()
result.safe_search_annotation.adult = 1
result.safe_search_annotation.violence = 1
vision_client.safe_search_detection = MagicMock(return_value=result)

filename = str(uuid.uuid4())
data = {
'bucket': 'my-bucket',
'name': filename
}

main.blur_offensive_images(data, None)

out, _ = capsys.readouterr()

assert 'Analyzing %s.' % filename in out
assert 'The image %s was detected as OK.' % filename in out
assert __blur_image.called is False


@patch('main.os')
@patch('main.Image')
def test_blur_image(image_mock, os_mock, capsys):
filename = str(uuid.uuid4())

os_mock.remove = MagicMock()
os_mock.path = MagicMock()
os_mock.path.basename = MagicMock(side_effect=(lambda x: x))

image_mock.return_value = image_mock
image_mock.__enter__.return_value = image_mock

blob = UserDict()
blob.name = filename
blob.bucket = UserDict()
blob.bucket.blob = MagicMock(return_value=blob)
blob.download_to_filename = MagicMock()
blob.upload_from_filename = MagicMock()

main.__blur_image(blob)

out, _ = capsys.readouterr()

assert f'Image {filename} was downloaded to' in out
assert f'Image {filename} was blurred.' in out
assert f'Blurred image was uploaded to blurred-{filename}.' in out
assert os_mock.remove.called
assert image_mock.resize.called
3 changes: 3 additions & 0 deletions functions/imagemagick/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
google-cloud-vision==0.33.0
google-cloud-storage==1.11.0
Wand==0.4.4

0 comments on commit 06cc18c

Please sign in to comment.