diff --git a/.gitignore b/.gitignore index b71ef365088..9933e9cbd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ tags # Gradle out/ + +# OSX +.DS_Store diff --git a/pom.xml b/pom.xml index f9d9d0d28e4..20c9254cb0f 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,7 @@ translate translate/cloud-client unittests + vision/cloud-client vision/face-detection vision/label vision/landmark-detection diff --git a/vision/cloud-client/README.md b/vision/cloud-client/README.md new file mode 100644 index 00000000000..06c8ec1d706 --- /dev/null +++ b/vision/cloud-client/README.md @@ -0,0 +1,40 @@ +# Image Feature Detection Sample + +[Google Cloud Vision API][vision] provides feature detection for images. +This API is part of the larger collection of Cloud Machine Learning APIs. + +This sample Java application demonstrates how to access the Cloud Vision API +using the [Google Cloud Client Library for Java][google-cloud-java]. + +[vision]: https://cloud.google.com/vision/docs/ +[google-cloud-java]: https://github.com/GoogleCloudPlatform/google-cloud-java + +## Build the sample + +Install [Maven](http://maven.apache.org/). + +Build your project with: + +``` +mvn clean compile assembly:single +``` + +You can then run a given `ClassName` via: + +``` +mvn exec:java -Dexec.mainClass=com.example.vision.ClassName \ + -DpropertyName=propertyValue \ + -Dexec.args="arg1 'arg 2' arg3" +``` + +### Analyze an image + +``` +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-project-credentials.json +``` + +``` +java -cp target/vision-google-cloud-samples-1.0.0-jar-with-dependencies.jar \ + com.example.vision.Detect \ + logos "./resources/logos.png" +``` diff --git a/vision/cloud-client/pom.xml b/vision/cloud-client/pom.xml new file mode 100644 index 00000000000..0ddf65675b4 --- /dev/null +++ b/vision/cloud-client/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + com.example.vision + vision-google-cloud-samples + jar + + + + doc-samples + com.google.cloud + 1.0.0 + ../.. + + + + 1.8 + 1.8 + UTF-8 + + + + + + com.google.cloud + google-cloud-vision + 0.8.1-alpha + + + + + + junit + junit + 4.12 + test + + + com.google.truth + truth + 0.31 + test + + + + + + maven-assembly-plugin + + + + com.example.vision.Detect + + + + jar-with-dependencies + + + + + + diff --git a/vision/cloud-client/resources/face_no_surprise.jpg b/vision/cloud-client/resources/face_no_surprise.jpg new file mode 100644 index 00000000000..0e2894adb83 Binary files /dev/null and b/vision/cloud-client/resources/face_no_surprise.jpg differ diff --git a/vision/cloud-client/resources/landmark.jpg b/vision/cloud-client/resources/landmark.jpg new file mode 100644 index 00000000000..41c3d0fc935 Binary files /dev/null and b/vision/cloud-client/resources/landmark.jpg differ diff --git a/vision/cloud-client/resources/logos.png b/vision/cloud-client/resources/logos.png new file mode 100644 index 00000000000..dcfb4ac955f Binary files /dev/null and b/vision/cloud-client/resources/logos.png differ diff --git a/vision/cloud-client/resources/text.jpg b/vision/cloud-client/resources/text.jpg new file mode 100644 index 00000000000..3b17d55de0e Binary files /dev/null and b/vision/cloud-client/resources/text.jpg differ diff --git a/vision/cloud-client/resources/wakeupcat.jpg b/vision/cloud-client/resources/wakeupcat.jpg new file mode 100644 index 00000000000..139cf461eca Binary files /dev/null and b/vision/cloud-client/resources/wakeupcat.jpg differ diff --git a/vision/cloud-client/src/main/java/com/example/vision/Detect.java b/vision/cloud-client/src/main/java/com/example/vision/Detect.java new file mode 100644 index 00000000000..28423f432da --- /dev/null +++ b/vision/cloud-client/src/main/java/com/example/vision/Detect.java @@ -0,0 +1,390 @@ +/** + * Copyright 2017, 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. + */ + +package com.example.vision; + + +import com.google.cloud.vision.spi.v1.ImageAnnotatorClient; +import com.google.cloud.vision.v1.AnnotateImageRequest; +import com.google.cloud.vision.v1.AnnotateImageResponse; +import com.google.cloud.vision.v1.BatchAnnotateImagesResponse; +import com.google.cloud.vision.v1.ColorInfo; +import com.google.cloud.vision.v1.DominantColorsAnnotation; +import com.google.cloud.vision.v1.EntityAnnotation; +import com.google.cloud.vision.v1.FaceAnnotation; +import com.google.cloud.vision.v1.Feature; +import com.google.cloud.vision.v1.Feature.Type; +import com.google.cloud.vision.v1.Image; +import com.google.cloud.vision.v1.LocationInfo; +import com.google.cloud.vision.v1.SafeSearchAnnotation; +import com.google.protobuf.ByteString; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +public class Detect { + + /** + * Detects entities,sentiment and syntax in a document using the Natural Language API. + * @throws IOException on Input/Output errors. + */ + public static void main(String[] args) throws IOException { + argsHelper(args, System.out); + } + + /** + * Helper that handles the input passed to the program. + * @throws IOException on Input/Output errors. + */ + public static void argsHelper(String[] args, PrintStream out) throws IOException { + if (args.length < 1) { + out.println("Usage:"); + out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tall-local | faces | labels | landmarks | logos | text | safe-search | properties\n" + + "Path:\n\tA file path (ex: ./resources/wakeupcat.jpg) or a URI for a Cloud Storage " + + "resource (gs://...)\n", + Detect.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + Detect app = new Detect(ImageAnnotatorClient.create()); + if (command.equals("all-local")) { + detectFaces("resources/face_no_surprise.jpg", out); + detectLabels("resources/wakeupcat.jpg", out); + detectLandmarks("resources/landmark.jpg", out); + detectLogos("resources/logos.png", out); + detectText("resources/text.jpg", out); + detectProperties("resources/landmark.jpg", out); + detectSafeSearch("resources/wakeupcat.jpg", out); + } else if (command.equals("faces")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectFaces(path, out); + } + } else if (command.equals("labels")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectLabels(path, out); + } + } else if (command.equals("landmarks")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectLandmarks(path, out); + } + } else if (command.equals("logos")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectLogos(path, out); + } + } else if (command.equals("text")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectText(path, out); + } + } else if (command.equals("properties")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectProperties(path, out); + } + } else if (command.equals("safe-search")) { + if (path.startsWith("gs://")) { + // TODO: See https://goo.gl/uWgYhQ + } else { + detectSafeSearch(path, out); + } + } + } + + private static ImageAnnotatorClient visionApi; + + /** + * Constructs a {@link Detect} which connects to the Cloud Vision API. + */ + public Detect(ImageAnnotatorClient client) { + visionApi = client; + } + + /** + * Detects faces in the specified image. + * @param filePath The path to the file to perform face detection on. + * @param out A {@link PrintStream} to write detected features to. + * @throws IOException on Input/Output errors. + */ + public static void detectFaces(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.FACE_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + for (FaceAnnotation annotation : res.getFaceAnnotationsList()) { + out.printf("anger: %s\njoy: %s\nsurprise: %s\nposition: %s", + annotation.getAngerLikelihood(), + annotation.getJoyLikelihood(), + annotation.getSurpriseLikelihood(), + annotation.getBoundingPoly()); + } + } + } + + /** + * Detects labels in the specified image. + * @param filePath The path to the file to perform label detection on. + * @param out A {@link PrintStream} to write detected labels to. + * @throws IOException on Input/Output errors. + */ + public static void detectLabels(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.LABEL_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + for (EntityAnnotation annotation : res.getLabelAnnotationsList()) { + annotation.getAllFields().forEach((k, v)->out.printf("%s : %s\n", k, v.toString())); + } + } + } + + /** + * Detects landmarks in the specified image. + * @param filePath The path to the file to perform landmark detection on. + * @param out A {@link PrintStream} to write detected landmarks to. + * @throws IOException on Input/Output errors. + */ + public static void detectLandmarks(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.LANDMARK_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + for (EntityAnnotation annotation : res.getLandmarkAnnotationsList()) { + LocationInfo info = annotation.getLocationsList().listIterator().next(); + out.printf("Landmark: %s\n %s\n", annotation.getDescription(), info.getLatLng()); + } + } + } + + /** + * Detects logos in the specified image. + * @param filePath The path to the file to perform logo detection on. + * @param out A {@link PrintStream} to write detected logos to. + * @throws IOException on Input/Output errors. + */ + public static void detectLogos(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.LOGO_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + for (EntityAnnotation annotation : res.getLogoAnnotationsList()) { + out.println(annotation.getDescription()); + } + } + } + + /** + * Detects text in the specified image. + * @param filePath The path to the file to detect text in. + * @param out A {@link PrintStream} to write the detected text to. + * @throws IOException on Input/Output errors. + */ + public static void detectText(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.TEXT_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + for (EntityAnnotation annotation : res.getTextAnnotationsList()) { + out.printf("Text: %s\n", annotation.getDescription()); + out.printf("Position : %s\n", annotation.getBoundingPoly()); + } + } + } + + /** + * Detects image properties such as color frequency from the specified image. + * @param filePath The path to the file to detect properties. + * @param out A {@link PrintStream} to write + * @throws IOException on Input/Output errors. + */ + public static void detectProperties(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.IMAGE_PROPERTIES).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + DominantColorsAnnotation colors = res.getImagePropertiesAnnotation().getDominantColors(); + for (ColorInfo color : colors.getColorsList()) { + out.printf("fraction: %f\nr: %f, g: %f, b: %f\n", + color.getPixelFraction(), + color.getColor().getRed(), + color.getColor().getGreen(), + color.getColor().getBlue()); + } + } + } + + /** + * Detects whether the specified image has features you would want to moderate. + * @param filePath The path to the file used for safe search detection. + * @param out A {@link PrintStream} to write the results to. + * @throws IOException on Input/Output errors. + */ + public static void detectSafeSearch(String filePath, PrintStream out) throws IOException { + List requests = new ArrayList<>(); + + ByteString imgBytes = ByteString.readFrom(new FileInputStream(filePath)); + + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.SAFE_SEARCH_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + BatchAnnotateImagesResponse response = visionApi.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + // For full list of available annotations, see http://g.co/cloud/vision/docs + SafeSearchAnnotation annotation = res.getSafeSearchAnnotation(); + out.printf("adult: %s\nmedical: %s\nspoofed: %s\nviolence: %s\n", + annotation.getAdult(), + annotation.getMedical(), + annotation.getSpoof(), + annotation.getViolence()); + } + } +} diff --git a/vision/cloud-client/src/main/java/com/example/vision/QuickstartSample.java b/vision/cloud-client/src/main/java/com/example/vision/QuickstartSample.java new file mode 100644 index 00000000000..f75307f71cd --- /dev/null +++ b/vision/cloud-client/src/main/java/com/example/vision/QuickstartSample.java @@ -0,0 +1,76 @@ +/* + Copyright 2017, 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. +*/ + +package com.example.vision; + +// [START vision_quickstart] +// Imports the Google Cloud client library +import com.google.cloud.vision.spi.v1.ImageAnnotatorClient; +import com.google.cloud.vision.v1.AnnotateImageRequest; +import com.google.cloud.vision.v1.AnnotateImageResponse; +import com.google.cloud.vision.v1.BatchAnnotateImagesResponse; +import com.google.cloud.vision.v1.EntityAnnotation; +import com.google.cloud.vision.v1.Feature; +import com.google.cloud.vision.v1.Feature.Type; +import com.google.cloud.vision.v1.Image; +import com.google.protobuf.ByteString; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class QuickstartSample { + public static void main(String... args) throws Exception { + // Instantiates a client + ImageAnnotatorClient vision = ImageAnnotatorClient.create(); + + // The path to the image file to annotate + String fileName = "./resources/wakeupcat.jpg"; + + // Reads the image file into memory + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + ByteString imgBytes = ByteString.copyFrom(data); + + // Builds the image annotation request + List requests = new ArrayList<>(); + Image img = Image.newBuilder().setContent(imgBytes).build(); + Feature feat = Feature.newBuilder().setType(Type.LABEL_DETECTION).build(); + AnnotateImageRequest request = AnnotateImageRequest.newBuilder() + .addFeatures(feat) + .setImage(img) + .build(); + requests.add(request); + + // Performs label detection on the image file + BatchAnnotateImagesResponse response = vision.batchAnnotateImages(requests); + List responses = response.getResponsesList(); + + for (AnnotateImageResponse res : responses) { + if (res.hasError()) { + System.out.printf("Error: %s\n", res.getError().getMessage()); + return; + } + + for (EntityAnnotation annotation : res.getLabelAnnotationsList()) { + annotation.getAllFields().forEach((k, v)->System.out.printf("%s : %s\n", k, v.toString())); + } + } + } +} +// [END vision_quickstart] diff --git a/vision/cloud-client/src/test/java/com/example/vision/DetectIT.java b/vision/cloud-client/src/test/java/com/example/vision/DetectIT.java new file mode 100644 index 00000000000..06af01b940e --- /dev/null +++ b/vision/cloud-client/src/test/java/com/example/vision/DetectIT.java @@ -0,0 +1,148 @@ +/* + 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. +*/ + +package com.example.vision; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.vision.spi.v1.ImageAnnotatorClient; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +/** + * Tests for vision "Detect" sample. + */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIT { + private ByteArrayOutputStream bout; + private PrintStream out; + private Detect app; + + @Before + public void setUp() throws IOException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + app = new Detect(ImageAnnotatorClient.create()); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testDetectEmptyArgs() throws Exception { + // Act + String[] args = {}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("all-local | faces | labels | landmarks |" + + " logos | text | safe-search | properties"); + } + + @Test + public void testFaces() throws Exception { + // Act + String[] args = {"faces", "./resources/face_no_surprise.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("anger: POSSIBLE"); + assertThat(got).contains("joy: POSSIBLE"); + assertThat(got).contains("surprise: UNLIKELY"); + } + + @Test + public void testLabels() throws Exception { + // Act + String[] args = {"labels", "./resources/wakeupcat.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("whiskers"); + } + + @Test + public void testLandmarks() throws Exception { + // Act + String[] args = {"landmarks", "./resources/landmark.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("Palace of Fine Arts"); + } + + @Test + public void testLogos() throws Exception { + // Act + String[] args = {"logos", "./resources/logos.png"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("Google"); + } + + @Test + public void testText() throws Exception { + // Act + String[] args = {"text", "./resources/text.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("37%"); + } + + @Test + public void testSafeSearch() throws Exception { + // Act + String[] args = {"safe-search", "./resources/wakeupcat.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("adult: VERY_UNLIKELY"); + } + + @Test + public void testProperties() throws Exception { + // Act + String[] args = {"properties", "./resources/landmark.jpg"}; + Detect.argsHelper(args, out); + + // Assert + String got = bout.toString(); + assertThat(got).contains("fraction:"); + assertThat(got).contains("r:"); + assertThat(got).contains("g:"); + assertThat(got).contains("b:"); + } +}