8000 [Cloud Run] Image processing tutorial (#1618) · RamsesMartinez/java-docs-samples@212c43d · GitHub 8000
[go: up one dir, main page]

Skip to content

Commit 212c43d

Browse files
authored
[Cloud Run] Image processing tutorial (GoogleCloudPlatform#1618)
* draft * Working upload * basic app complete * linting, clean up and tests * clean up pom * fix style * Add region tags * fix region_tag * update README * draft * Working upload * basic app complete * linting, clean up and tests * clean up pom * fix style * Add region tags * fix region_tag * update README * Review comments
1 parent 6423b40 commit 212c43d

File tree

11 files changed

+588
-3
lines changed

11 files changed

+588
-3
lines changed

run/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This directory contains samples for [Google Cloud Run](https://cloud.run). [Clou
1313
| ------------------------------- | ------------------------ | ------------- |
1414
|[Hello World][helloworld]&nbsp;&#10149; | Quickstart | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_helloworld] |
1515
|[Cloud Pub/Sub][pubsub] | Handling Pub/Sub push messages | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_pubsub] |
16+
|[Image Magick][image-processing] | Event-driven image analysis & transformation | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_img] |
1617

1718
For more Cloud Run samples beyond Java, see the main list in the [Cloud Run Samples repository](https://github.com/GoogleCloudPlatform/cloud-run-samples).
1819

@@ -126,8 +127,9 @@ gcloud beta run deploy $SAMPLE \
126127
[run_deploy]: https://cloud.google.com/run/docs/deploying
127128
[helloworld]: https://github.com/knative/docs/tree/master/docs/serving/samples/hello-world/helloworld-java
128129
[pubsub]: pubsub/
129-
[run_button_helloworld]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/knative/docs&cloudshell_working_dir=docs/serving/samples/hello-world/helloworld-java
130-
[run_button_pubsub]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&cloudshell_working_dir=run/cloudrun-pubsub
130+
[run_button_helloworld]: https://deploy.cloud.run/?git_repo=https://github.com/knative/docs&dir=docs/serving/samples/hello-world/helloworld-java
131+
[run_button_pubsub]: https://deploy.cloud.run/?git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&dir=run/pubsub
132+
[run_button_image]: https://deploy.cloud.run/?git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&dir=run/image-processing
131133
[push-pull]: https://cloud.google.com/container-registry/docs/pushing-and-pulling
132134
[jib]: https://github.com/GoogleContainerTools/jib
133135
[jib-tutorial]: https://github.com/GoogleContainerTools/jib/tree/master/examples/spring-boot

run/image-processing/Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2019 Google LLC. All rights reserved.
2+
# Use of this source code is governed by the Apache 2.0
3+
# license that can be found in the LICENSE file.
4+
5+
# Use the official maven/Java 11 image to create a build artifact.
6+
# https://hub.docker.com/_/maven
7+
FROM maven:3.6.2-jdk-11 as builder
8+
9+
# Copy local code to the container image.
10+
WORKDIR /app
11+
COPY pom.xml .
12+
COPY src ./src
13+
14+
# Build a release artifact.
15+
RUN mvn package -DskipTests
16+
17+
# Use AdoptOpenJDK for base image.
18+
# It's important to use OpenJDK 8u191 or above that has container support enabled.
19+
# https://hub.docker.com/r/adoptopenjdk/openjdk11
20+
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
21+
FROM adoptopenjdk/openjdk11:alpine
22+
23+
# Install Imagemagick into the container image.
24+
# For more on system packages review the system packages tutorial.
25+
# https://cloud.google.com/run/docs/tutorials/system-packages#dockerfile
26+
RUN apk add --no-cache imagemagick
27+
28+
# Copy the jar to the production image from the builder stage.
29+
COPY --from=builder /app/target/image-processing-*.jar /image-processing.jar
30+
31+
# Run the web service on container startup.
32+
CMD ["java","-Dserver.port=${PORT}","-jar","/image-processing.jar"]

run/image-processing/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Cloud Run Image Processing Sample
2+
3+
This sample service applies [Cloud Storage](https://cloud.google.com/storage/docs)-triggered image processing with [Cloud Vision API](https://cloud.google.com/vision/docs) analysis and ImageMagick transformation.
4+
5+
Use it with the [Image Processing with Cloud Run tutorial](http://cloud.google.com/run/docs/tutorials/image-processing).
6+
7+
For more details on how to work with this sample read the [Google Cloud Run Java Samples README](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/run).
8+
9+
[![Run in Google Cloud][run_img]][run_link]
10+
11+
## Dependencies
12+
13+
* **Spring Boot**: Web server framework.
14+
* **Image Magick**: for image processing.
15+
* **@google-cloud/storage**: Google Cloud Storage client library.
16+
* **@google-cloud/vision**: Cloud Vision API client library.
17+
18+
## Environment Variables
19+
20+
Cloud Run services can be [configured with Environment Variables](https://cloud.google.com/run/docs/configuring/environment-variables).
21+
Required variables for this sample include:
22+
23+
* `BLURRED_BUCKET_NAME`: The Cloud Run service will write blurred images to this Cloud Storage bucket.
24+
25+
[run_img]: https://storage.googleapis.com/cloudrun/button.svg
26+
[run_link]: https://deploy.cloud.run/?git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&dir=run/image-processing

run/image-processing/pom.xml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--Copyright 2019 Google LLCLicensed 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 athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed 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 andlimitations under the License.-->
3+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>com.example.cloudrun</groupId>
6+
<artifactId>image-processing</artifactId>
7+
<version>0.0.1-SNAPSHOT</version>
8+
<!-- The parent pom defines common style checks and testing strategies for our samples. Removing or replacing it should not affect the execution of the samples in anyway. -->
9+
<parent>
10+
<groupId>com.google.cloud.samples</groupId>
11+
<artifactId>shared-configuration</artifactId>
12+
<version>1.0.11</version>
13+
</parent>
14+
<properties>
15+
<maven.compiler.target>11</maven.compiler.target>
16+
<maven.compiler.source>11</maven.compiler.source>
17+
</properties>
18+
<dependencyManagement>
19+
<dependencies>
20+
<dependency>
21+
<!-- Import dependency management from Spring Boot -->
22+
<groupId>org.springframework.boot</groupId>
23+
<artifactId>spring-boot-dependencies</artifactId>
24+
<version>2.1.9.RELEASE</version>
25+
<type>pom</type>
26+
<scope>import</scope>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.cloud</groupId>
30+
<artifactId>spring-cloud-dependencies</artifactId>
31+
<version>Greenwich.SR1</version>
32+
<type>pom</type>
33+
<scope>import</scope>
34+
</dependency>
35+
<!-- // [START run_imageproc_dep_management] -->
36+
<dependency>
37+
<groupId>org.springframework.cloud</groupId>
38+
<artifactId>spring-cloud-gcp-dependencies</artifactId>
39+
<version>1.1.3.RELEASE</version>
40+
<type>pom</type>
41+
<scope>import</scope>
42+
</dependency>
43+
<!-- // [END run_imageproc_dep_management] -->
44+
</dependencies>
45+
</dependencyManagement>
46+
<dependencies>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-web</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.apache.commons</groupId>
53+
<artifactId>commons-lang3</artifactId>
54+
<version>3.9</version>
55+
</dependency>
56+
<dependency>
57+
<groupId>org.springframework.boot</groupId>
58+
<artifactId>spring-boot-starter-test</artifactId>
59+
<scope>test</scope>
60+
</dependency>
61+
<!-- // [START run_imageproc_dep] -->
62+
<dependency>
63+
<groupId>com.google.code.gson</groupId>
64+
<artifactId>gson</artifactId>
65+
<version>2.8.6</version>
66+
<scope>compile</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>org.json</groupId>
70+
<artifactId>json</artifactId>
71+
<version>20190722</version>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.springframework.cloud</groupId>
75+
<artifactId>spring-cloud-gcp-starter-vision</artifactId>
76+
</dependency>
77+
<dependency>
78+
<groupId>org.springframework.cloud</groupId>
79+
<artifactId>spring-cloud-gcp-starter-storage</artifactId>
80+
</dependency>
81+
<dependency>
82+
<groupId>commons-io</groupId>
83+
<artifactId>commons-io</artifactId>
84+
<version>2.5</version>
85+
</dependency>
86+
<!-- // [END run_imageproc_dep] -->
87+
</dependencies>
88+
<build>
89+
<plugins>
90+
<plugin>
91+
<groupId>org.springframework.boot</groupId>
92+
<artifactId>spring-boot-maven-plugin</artifactId>
93+
<version>2.1.4.RELEASE</version>
94+
<executions>
95+
<execution>
96+
<goals>
97+
<goal>repackage</goal>
98+
</goals>
99+
</execution>
100+
</executions>
101+
</plugin>
102+
</plugins>
103+
</build>
104+
</project>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.cloudrun;
18+
19+
// Body.Message is the payload of a Pub/Sub event. Please refer to the docs for
20+
// additional information regarding Pub/Sub events.
21+
public class Body {
22+
23+
private Message message;
24+
25+
public Body() {}
26+
27+
public Message getMessage() {
28+
return message;
29+
}
30+
31+
public void setMessage(Message message) {
32+
this.message = message;
33+
}
34+
35+
public class Message {
36+
37+
private String messageId;
38+
private String publishTime;
39+
private String data;
40+
41+
public Message() {}
42+
43+
public Message(String messageId, String publishTime, String data) {
44+
this.messageId = messageId;
45+
this.publishTime = publishTime;
46+
this.data = data;
47+
}
48+
49+
public String getMessageId() {
50+
return messageId;
51+
}
52+
53+
public void setMessageId(String messageId) {
54+
this.messageId = messageId;
55+
}
56+
57+
public String getPublishTime() {
58+
return publishTime;
59+
}
60+
61+
public void setPublishTime(String publishTime) {
62+
this.publishTime = publishTime;
63+
}
64+
65+
public String getData() {
66+
return data;
67+
}
68+
69+
public void setData(String data) {
70+
this.data = data;
71+
}
72+
}
73+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.cloudrun;
18+
19+
// [START run_imageproc_handler_setup]
20+
import com.google.cloud.storage.Blob;
21+
import com.google.cloud.storage.BlobId;
22+
import com.google.cloud.storage.BlobInfo;
23+
import com.google.cloud.storage.Storage;
24+
import com.google.cloud.storage.StorageOptions;
25+
import com.google.cloud.vision.v1.AnnotateImageRequest;
26+
import com.google.cloud.vision.v1.AnnotateImageResponse;
27+
import com.google.cloud.vision.v1.BatchAnnotateImagesResponse;
28+
import com.google.cloud.vision.v1.Feature;
29+
import com.google.cloud.vision.v1.Feature.Type;
30+
import com.google.cloud.vision.v1.Image;
31+
import com.google.cloud.vision.v1.ImageAnnotatorClient;
32+
import com.google.cloud.vision.v1.ImageSource;
33+
import com.google.cloud.vision.v1.SafeSearchAnnotation;
34+
import com.google.gson.JsonObject;
35+
import java.io.IOException;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.nio.file.Paths;
39+
import java.util.ArrayList;
40+
import java.util.List;
41+
42+
public class ImageMagick {
43+
44+
private static final String BLURRED_BUCKET_NAME = System.getenv("BLURRED_BUCKET_NAME");
45+
private static Storage storage = StorageOptions.getDefaultInstance().getService();
46+
// [END run_imageproc_handler_setup]
47+
48+
// [START run_imageproc_handler_analyze]
49+
// Blurs uploaded images that are flagged as Adult or Violence.
50+
public static void blurOffensiveImages(JsonObject data) {
51+
String fileName = data.get("name").getAsString();
52+
String bucketName = data.get("bucket").getAsString();
53+
BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, fileName).build();
54+
// Construct URI to GCS bucket and file.
55+
String gcsPath = String.format("gs://%s/%s", bucketName, fileName);
56+
System.out.println(String.format("Analyzing %s", fileName));
57+
58+
// Construct request.
59+
List<AnnotateImageRequest> requests = new ArrayList<>();
60+
ImageSource imgSource = ImageSource.newBuilder().setImageUri(gcsPath).build();
61+
Image img = Image.newBuilder().setSource(imgSource).build();
62+
Feature feature = Feature.newBuilder().setType(Type.SAFE_SEARCH_DETECTION).build();
63+
AnnotateImageRequest request =
64+
AnnotateImageRequest.newBuilder().addFeatures(feature).setImage(img).build();
65+
requests.add(request);
66+
67+
// Send request to the Vision API.
68+
try (ImageAnnotatorClient client = ImageAnnotatorClient.create()) {
69+
BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
70+
List<AnnotateImageResponse> responses = response.getResponsesList();
71+
for (AnnotateImageResponse res : responses) {
72+
if (res.hasError()) {
73+
System.out.println(String.format("Error: %s\n", res.getError().getMessage()));
74+
return;
75+
}
76+
// Get Safe Search Annotations
77+
SafeSearchAnnotation annotation = res.getSafeSearchAnnotation();
78+
if (annotation.getAdultValue() == 5 || annotation.getViolenceValue() == 5) {
79+
System.out.println(String.format("Detected %s as inappropriate.", fileName));
80+
blur(blobInfo);
81+
} else {
82+
System.out.println(String.format("Detected %s as OK.", fileName));
83+
}
84+
}
85+
} catch (Exception e) {
86+
System.out.println(String.format("Error with Vision API: %s", e.getMessage()));
87+
}
88+
}
89+
// [END run_imageproc_handler_analyze]
90+
91+
// [START run_imageproc_handler_blur]
92+
// Blurs the file described by blobInfo using ImageMagick,
93+
// and uploads it to the blurred bucket.
94+
public static void blur(BlobInfo blobInfo) throws IOException {
95+
String bucketName = blobInfo.getBucket();
96+
String fileName = blobInfo.getName();
97+
// Download image
98+
Blob blob = storage.get(BlobId.of(bucketName, fileName));
99+
Path download = Paths.get("/tmp/", fileName);
100+
blob.downloadTo(download);
101+
102+
// Construct the command.
103+
List<String> args = new ArrayList<String>();
104+
args.add("convert");
105+
args.add(download.toString());
106+
args.add("-blur");
107+
args.add("0x8");
108+
Path upload = Paths.get("/tmp/", "blurred-" + fileName);
109+
args.add(upload.toString());
110+
try {
111+
ProcessBuilder pb = new ProcessBuilder(args);
112+
Process process = pb.start();
113+
process.waitFor();
114+
} catch (Exception e) {
115+
System.out.println(String.format("Error: %s", e.getMessage()));
116+
}
117+
118+
// Upload image to blurred bucket.
119+
BlobId blurredBlobId = BlobId.of(BLURRED_BUCKET_NAME, fileName);
120+
BlobInfo blurredBlobInfo =
121+
BlobInfo.newBuilder(blurredBlobId).setContentType(blob.getContentType()).build();
122+
try {
123+
byte[] blurredFile = Files.readAllBytes(upload);
124+
Blob blurredBlob = storage.create(blurredBlobInfo, blurredFile);
125+
System.out.println(
126+
String.format("Blurred image uploaded to: gs://%s/%s", BLURRED_BUCKET_NAME, fileName));
127+
} catch (Exception e) {
128+
System.out.println(String.format("Error in upload: %s", e.getMessage()));
129+
}
130+
131+
// Remove images from fileSystem
132+
Files.delete(download);
133+
Files.delete(upload);
134+
}
135+
}
136+
// [END run_imageproc_handler_blur]

0 commit comments

Comments
 (0)
0