Skip to content

Commit 06cc18c

Browse files
author
Ace Nassri
authored
Add GCF imagemagick samples (GoogleCloudPlatform#1684)
* 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
1 parent 9bacd2e commit 06cc18c

File tree

4 files changed

+242
-0
lines changed

4 files changed

+242
-0
lines changed

functions/imagemagick/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<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"/>
2+
3+
# Google Cloud Functions ImageMagick sample
4+
5+
This sample shows you how to blur an image using ImageMagick in a
6+
Storage-triggered Cloud Function.
7+
8+
View the [source code][code].
9+
10+
[code]: main.py
11+
12+
## Deploy and Test
13+
14+
1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud
15+
Functions for your project.
16+
17+
1. Clone this repository:
18+
19+
git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
20+
cd python-docs-samples/functions/imagemagick
21+
22+
1. Create a Cloud Storage Bucket:
23+
24+
gsutil mb gs://YOUR_BUCKET_NAME
25+
26+
This storage bucket is used to upload images for the function to check.
27+
28+
1. Deploy the `blur_offensive_images` function with a Storage trigger:
29+
30+
gcloud functions deploy blur_offensive_images --trigger-bucket=YOUR_BUCKET_NAME --runtime python37
31+
32+
* Replace `YOUR_BUCKET_NAME` with the name of the Cloud Storage Bucket you created earlier.
33+
34+
1. Upload an offensive image to the Storage bucket, such as this image of
35+
a flesh-eating zombie: https://cdn.pixabay.com/photo/2015/09/21/14/24/zombie-949916_1280.jpg
36+
37+
1. Check the logs for the `blur_offensive_images` function:
38+
39+
gcloud functions get-logs blur_offensive_images
40+
41+
You should see something like this in your console:
42+
43+
D ... User function triggered, starting execution
44+
I ... `The image zombie.jpg has been detected as inappropriate.`
45+
D ... Execution took 1 ms, user function completed successfully
46+
47+
[quickstart]: https://cloud.google.com/functions/quickstart

functions/imagemagick/main.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright 2018 Google LLC
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+
16+
# [START functions_imagemagick_setup]
17+
import os
18+
import tempfile
19+
20+
from google.cloud import storage, vision
21+
from wand.image import Image
22+
23+
storage_client = storage.Client()
24+
vision_client = vision.ImageAnnotatorClient()
25+
# [END functions_imagemagick_setup]
26+
27+
28+
# [START functions_imagemagick_analyze]
29+
# Blurs uploaded images that are flagged as Adult or Violence.
30+
def blur_offensive_images(data, context):
31+
file_data = data
32+
33+
file_name = file_data['name']
34+
bucket_name = file_data['bucket']
35+
36+
blob = storage_client.bucket(bucket_name).get_blob(file_name)
37+
blob_uri = f'gs://{bucket_name}/{file_name}'
38+
blob_source = {'source': {'image_uri': blob_uri}}
39+
40+
# Ignore already-blurred files
41+
if file_name.startswith('blurred-'):
42+
print(f'The image {file_name} is already blurred.')
43+
return
44+
45+
print(f'Analyzing {file_name}.')
46+
47+
result = vision_client.safe_search_detection(blob_source)
48+
detected = result.safe_search_annotation
49+
50+
# Process image
51+
if detected.adult == 5 or detected.violence == 5:
52+
print(f'The image {file_name} was detected as inappropriate.')
53+
return __blur_image(blob)
54+
else:
55+
print(f'The image {file_name} was detected as OK.')
56+
# [END functions_imagemagick_analyze]
57+
58+
59+
# [START functions_imagemagick_blur]
60+
# Blurs the given file using ImageMagick.
61+
def __blur_image(current_blob):
62+
file_name = current_blob.name
63+
_, temp_local_filename = tempfile.mkstemp()
64+
65+
# Download file from bucket.
66+
current_blob.download_to_filename(temp_local_filename)
67+
print(f'Image {file_name} was downloaded to {temp_local_filename}.')
68+
69+
# Blur the image using ImageMagick.
70+
with Image(filename=temp_local_filename) as image:
71+
image.resize(*image.size, blur=16, filter='hamming')
72+
image.save(filename=temp_local_filename)
73+
74+
print(f'Image {file_name} was blurred.')
75+
76+
# Send Blurred image back to the bucket (with a 'blurred-' prefix).
77+
# The prefix is necessary to avoid re-invoking the function upon upload.
78+
new_file_name = f'blurred-{file_name}'
79+
new_blob = current_blob.bucket.blob(new_file_name)
80+
new_blob.upload_from_filename(temp_local_filename)
81+
print(f'Blurred image was uploaded to {new_file_name}.')
82+
83+
# Delete the temporary file.
84+
os.remove(temp_local_filename)
85+
# [END functions_imagemagick_blur]

functions/imagemagick/main_test.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2018 Google LLC
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+
from collections import UserDict
16+
import uuid
17+
18+
from mock import MagicMock, patch
19+
20+
import main
21+
22+
23+
@patch('main.__blur_image')
24+
@patch('main.vision_client')
25+
@patch('main.storage_client')
26+
def test_process_offensive_image(
27+
storage_client,
28+
vision_client,
29+
__blur_image,
30+
capsys):
31+
result = UserDict()
32+
result.safe_search_annotation = UserDict()
33+
result.safe_search_annotation.adult = 5
34+
result.safe_search_annotation.violence = 5
35+
vision_client.safe_search_detection = MagicMock(return_value=result)
36+
37+
filename = str(uuid.uuid4())
38+
data = {
39+
'bucket': 'my-bucket',
40+
'name': filename
41+
}
42+
43+
main.blur_offensive_images(data, None)
44+
45+
out, _ = capsys.readouterr()
46+
assert 'Analyzing %s.' % filename in out
47+
assert 'The image %s was detected as inappropriate.' % filename in out
48+
assert main.__blur_image.called
49+
50+
51+
@patch('main.__blur_image')
52+
@patch('main.vision_client')
53+
@patch('main.storage_client')
54+
def test_process_safe_image(
55+
storage_client,
56+
vision_client,
57+
__blur_image,
58+
capsys):
59+
result = UserDict()
60+
result.safe_search_annotation = UserDict()
61+
result.safe_search_annotation.adult = 1
62+
result.safe_search_annotation.violence = 1
63+
vision_client.safe_search_detection = MagicMock(return_value=result)
64+
65+
filename = str(uuid.uuid4())
66+
data = {
67+
'bucket': 'my-bucket',
68+
'name': filename
69+
}
70+
71+
main.blur_offensive_images(data, None)
72+
73+
out, _ = capsys.readouterr()
74+
75+
assert 'Analyzing %s.' % filename in out
76+
assert 'The image %s was detected as OK.' % filename in out
77+
assert __blur_image.called is False
78+
79+
80+
@patch('main.os')
81+
@patch('main.Image')
82+
def test_blur_image(image_mock, os_mock, capsys):
83+
filename = str(uuid.uuid4())
84+
85+
os_mock.remove = MagicMock()
86+
os_mock.path = MagicMock()
87+
os_mock.path.basename = MagicMock(side_effect=(lambda x: x))
88+
89+
image_mock.return_value = image_mock
90+
image_mock.__enter__.return_value = image_mock
91+
92+
blob = UserDict()
93+
blob.name = filename
94+
blob.bucket = UserDict()
95+
blob.bucket.blob = MagicMock(return_value=blob)
96+
blob.download_to_filename = MagicMock()
97+
blob.upload_from_filename = MagicMock()
98+
99+
main.__blur_image(blob)
100+
101+
out, _ = capsys.readouterr()
102+
103+
assert f'Image {filename} was downloaded to' in out
104+
assert f'Image {filename} was blurred.' in out
105+
assert f'Blurred image was uploaded to blurred-{filename}.' in out
106+
assert os_mock.remove.called
107+
assert image_mock.resize.called
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
google-cloud-vision==0.33.0
2+
google-cloud-storage==1.11.0
3+
Wand==0.4.4

0 commit comments

Comments
 (0)