--- /dev/null
+/*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
+
+ https://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 org.tensorflow.ovic;
+
+import android.graphics.Bitmap;
+import android.os.SystemClock;
+import android.util.Log;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+
+/**
+ * Class that benchmarks image classifier models.
+ *
+ * <p>===================== General workflow =======================
+ *
+ * <pre>{@code
+ * benchmarker = new OvicBenchmarker();
+ * benchmarker.getReadyToTest(labelInputStream, model);
+ * while (!benchmarker.shouldStop()) {
+ * Bitmap bitmap = ...
+ * benchmarker.doTestIteration(bitmap);
+ * }
+ * }</pre>
+ */
+public class OvicBenchmarker {
+ /** Tag for the {@link Log}. */
+ private static final String TAG = "OvicBenchmarker";
+
+ /** Evaluation transformation parameters. */
+ private static final float CENTRAL_FRACTION = 0.875f;
+
+ /** Dimensions of inputs. */
+ private static final int DIM_BATCH_SIZE = 1;
+ private static final int DIM_PIXEL_SIZE = 3;
+ private int imgHeight = 224;
+ private int imgWidth = 224;
+
+ /* Preallocated buffers for storing image data in. */
+ private int[] intValues = null;
+
+ /** A ByteBuffer to hold image data, to be feed into classifier as inputs. */
+ private ByteBuffer imgData = null;
+
+ private OvicClassifier classifier;
+
+ /** Total runtime in ms. */
+ private double totalRuntime = 0.0;
+ /** Total allowed runtime in ms. */
+ private double wallTime = 20000 * 30.0;
+
+ private Boolean benchmarkStarted = null;
+
+ /**
+ * Initializes an {@link OvicBenchmarker}
+ *
+ * @param wallTime: a double number specifying the total amount of time to benchmark.
+ */
+ public OvicBenchmarker(double wallTime) {
+ benchmarkStarted = false;
+ totalRuntime = 0.0;
+ this.wallTime = wallTime;
+ }
+
+ /** Check whether the benchmarker should stop. */
+ public Boolean shouldStop() {
+ if (totalRuntime >= wallTime) {
+ Log.e(
+ TAG,
+ "Total runtime "
+ + Double.toString(totalRuntime)
+ + " exceeded walltime "
+ + Double.toString(wallTime));
+ return true;
+ }
+ return false;
+ }
+
+ /** Check whether the benchmarker is ready to start classifying images. */
+ public Boolean readyToTest() {
+ return (classifier != null);
+ }
+
+ /**
+ * Getting the benchmarker ready for classifying images.
+ *
+ * @param labelInputStream: an {@link InputStream} specifying where the list of labels should be
+ * read from.
+ * @param model: a {@link MappedByteBuffer} model to benchmark.
+ */
+ public void getReadyToTest(InputStream labelInputStream, MappedByteBuffer model) {
+ try {
+ Log.i(TAG, "Creating classifier.");
+ classifier = new OvicClassifier(labelInputStream, model);
+ int [] inputDims = classifier.getInputDims();
+ imgHeight = inputDims[1];
+ imgWidth = inputDims[2];
+ // Only accept QUANTIZED_UINT8 input.
+ imgData = ByteBuffer.allocateDirect(DIM_BATCH_SIZE * imgHeight * imgWidth * DIM_PIXEL_SIZE);
+ imgData.order(ByteOrder.nativeOrder());
+ intValues = new int[imgHeight * imgWidth];
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ Log.e(TAG, "Failed to initialize ImageNet classifier for the benchmarker.");
+ }
+ }
+
+ /** Return how many classes are predicted per image. */
+ public int getNumPredictions() {
+ return classifier.getNumPredictions();
+ }
+
+ /**
+ * Perform test on a single bitmap image.
+ *
+ * @param bitmap: a {@link Bitmap} image to classify.
+ */
+ public OvicSingleImageResult doTestIteration(Bitmap bitmap)
+ throws IOException, InterruptedException {
+ if (shouldStop() || !readyToTest()) {
+ return null;
+ }
+ OvicSingleImageResult iterResult = null;
+ try {
+ Log.i(TAG, "Converting bitmap.");
+ convertBitmapToInput(bitmap);
+ Log.i(TAG, "Classifying image.");
+ iterResult = classifier.classifyByteBuffer(imgData);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage());
+ Log.e(TAG, "Failed to classify image.");
+ }
+ if (iterResult == null || iterResult.latency == null) {
+ throw new RuntimeException("Classification result or timing is invalid.");
+ }
+ Log.d(TAG, "Native inference latency: " + iterResult.latency);
+ Log.i(TAG, iterResult.toString());
+
+ if (!benchmarkStarted) { // Skip the first image to discount warming-up time.
+ benchmarkStarted = true;
+ } else {
+ totalRuntime += (double) iterResult.latency;
+ }
+ return iterResult;
+ }
+
+ /**
+ * Writes Image data into a {@link ByteBuffer}.
+ *
+ * @param bitmap: a {@link Bitmap} source image.
+ */
+ private void convertBitmapToInput(Bitmap bitmap) throws RuntimeException {
+ if (imgData == null) {
+ throw new RuntimeException("Benchmarker is not yet ready to test.");
+ }
+ imgData.rewind();
+ // Perform transformations corresponding to evaluation mode.
+ float width = (float) bitmap.getWidth();
+ float height = (float) bitmap.getHeight();
+ int stWidth = Math.round((width - width * CENTRAL_FRACTION) / 2);
+ int stHeight = Math.round((height - height * CENTRAL_FRACTION) / 2);
+ int newWidth = Math.round(width - stWidth * 2);
+ int newHeight = Math.round(height - stHeight * 2);
+ bitmap = Bitmap.createBitmap(bitmap, stWidth, stHeight, newWidth, newHeight);
+ bitmap = Bitmap.createScaledBitmap(bitmap, imgWidth, imgHeight, true);
+ bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+
+ // Convert the image to ByteBuffer.
+ int pixel = 0;
+ long startTime = SystemClock.uptimeMillis();
+
+ for (int i = 0; i < imgHeight; ++i) {
+ for (int j = 0; j < imgWidth; ++j) {
+ final int val = intValues[pixel++];
+ imgData.put((byte) ((val >> 16) & 0xFF));
+ imgData.put((byte) ((val >> 8) & 0xFF));
+ imgData.put((byte) (val & 0xFF));
+ }
+ }
+ long endTime = SystemClock.uptimeMillis();
+ Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime));
+ }
+}
--- /dev/null
+/*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
+
+ https://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 org.tensorflow.ovic;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import org.tensorflow.lite.Interpreter;
+import org.tensorflow.lite.TestHelper;
+
+/** Benchmark ImageNet Classifier with Tensorflow Lite. */
+public class OvicClassifier {
+
+ /** Tag for the {@link Log}. */
+ private static final String TAG = "OvicClassifier";
+
+ /** Number of results to show (i.e. the "K" in top-K predictions). */
+ private static final int RESULTS_TO_SHOW = 5;
+
+ /** An instance of the driver class to run model inference with Tensorflow Lite. */
+ private Interpreter tflite;
+
+ /** Labels corresponding to the output of the vision model. */
+ private List<String> labelList;
+
+ /** An array to hold inference results, to be feed into Tensorflow Lite as outputs. */
+ private byte[][] inferenceOutputArray = null;
+ /** An array to hold final prediction probabilities. */
+ private float[][] labelProbArray = null;
+
+ /** Input resultion. */
+ private int[] inputDims = null;
+ /** Whether the model runs as float or quantized. */
+ private Boolean outputIsFloat = null;
+
+ private PriorityQueue<Map.Entry<Integer, Float>> sortedLabels =
+ new PriorityQueue<>(
+ RESULTS_TO_SHOW,
+ new Comparator<Map.Entry<Integer, Float>>() {
+ @Override
+ public int compare(Map.Entry<Integer, Float> o1, Map.Entry<Integer, Float> o2) {
+ return (o1.getValue()).compareTo(o2.getValue());
+ }
+ });
+
+ /** Initializes an {@code OvicClassifier}. */
+ OvicClassifier(InputStream labelInputStream, MappedByteBuffer model)
+ throws IOException, RuntimeException {
+ if (model == null) {
+ throw new RuntimeException("Input model is empty.");
+ }
+ labelList = loadLabelList(labelInputStream);
+ // OVIC uses one thread for CPU inference.
+ tflite = new Interpreter(model, 1);
+ inputDims = TestHelper.getInputDims(tflite, 0);
+ if (inputDims.length != 4) {
+ throw new RuntimeException("The model's input dimensions must be 4 (BWHC).");
+ }
+ if (inputDims[0] != 1) {
+ throw new RuntimeException("The model must have a batch size of 1, got "
+ + inputDims[0] + " instead.");
+ }
+ if (inputDims[3] != 3) {
+ throw new RuntimeException("The model must have three color channels, got "
+ + inputDims[3] + " instead.");
+ }
+ int minSide = Math.min(inputDims[1], inputDims[2]);
+ int maxSide = Math.max(inputDims[1], inputDims[2]);
+ if (minSide <= 0 || maxSide > 1000) {
+ throw new RuntimeException("The model's resolution must be between (0, 1000].");
+ }
+ String outputDataType = TestHelper.getOutputDataType(tflite, 0);
+ if (outputDataType.equals("float")) {
+ outputIsFloat = true;
+ } else if (outputDataType.equals("byte")) {
+ outputIsFloat = false;
+ } else {
+ throw new RuntimeException("Cannot process output type: " + outputDataType);
+ }
+ inferenceOutputArray = new byte[1][labelList.size()];
+ labelProbArray = new float[1][labelList.size()];
+ }
+
+ /** Classifies a {@link ByteBuffer} image. */
+ // @throws RuntimeException if model is uninitialized.
+ OvicSingleImageResult classifyByteBuffer(ByteBuffer imgData) throws RuntimeException {
+ if (tflite == null) {
+ throw new RuntimeException(TAG + ": ImageNet classifier has not been initialized; Failed.");
+ }
+ if (outputIsFloat == null) {
+ throw new RuntimeException(TAG + ": Classifier output type has not been resolved.");
+ }
+ if (outputIsFloat) {
+ tflite.run(imgData, labelProbArray);
+ } else {
+ tflite.run(imgData, inferenceOutputArray);
+ /** Convert results to float */
+ for (int i = 0; i < inferenceOutputArray[0].length; i++) {
+ labelProbArray[0][i] = (inferenceOutputArray[0][i] & 0xff) / 255.0f;
+ }
+ }
+ OvicSingleImageResult iterResult = computeTopKLabels();
+ iterResult.latency = getLastNativeInferenceLatencyMilliseconds();
+ return iterResult;
+ }
+
+ /** Return the probability array of all classes. */
+ public float[][] getlabelProbArray() {
+ return labelProbArray;
+ }
+
+ /** Return the number of top labels predicted by the classifier. */
+ public int getNumPredictions() {
+ return RESULTS_TO_SHOW;
+ }
+
+ /** Return the four dimensions of the input image. */
+ public int[] getInputDims() {
+ return inputDims;
+ }
+
+ /*
+ * Get native inference latency of last image classification run.
+ * @throws RuntimeException if model is uninitialized.
+ */
+ public Long getLastNativeInferenceLatencyMilliseconds() {
+ if (tflite == null) {
+ throw new RuntimeException(TAG + ": ImageNet classifier has not been initialized; Failed.");
+ }
+ Long latency = tflite.getLastNativeInferenceDurationNanoseconds();
+ return (latency == null) ? null : (Long) (latency / 1000000);
+ }
+
+ /** Closes tflite to release resources. */
+ public void close() {
+ tflite.close();
+ tflite = null;
+ }
+
+ /** Reads label list from Assets. */
+ private static List<String> loadLabelList(InputStream labelInputStream) throws IOException {
+ List<String> labelList = new ArrayList<String>();
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(labelInputStream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ labelList.add(line);
+ }
+ }
+ return labelList;
+ }
+
+ /** Computes top-K labels. */
+ private OvicSingleImageResult computeTopKLabels() {
+ if (labelList == null) {
+ throw new RuntimeException("Label file has not been loaded.");
+ }
+ for (int i = 0; i < labelList.size(); ++i) {
+ sortedLabels.add(new AbstractMap.SimpleEntry<>(i, labelProbArray[0][i]));
+ if (sortedLabels.size() > RESULTS_TO_SHOW) {
+ sortedLabels.poll();
+ }
+ }
+ OvicSingleImageResult singleImageResult = new OvicSingleImageResult();
+ if (sortedLabels.size() != RESULTS_TO_SHOW) {
+ throw new RuntimeException(
+ "Number of returned labels does not match requirement: "
+ + sortedLabels.size()
+ + " returned, but "
+ + RESULTS_TO_SHOW
+ + " required.");
+ }
+ for (int i = 0; i < RESULTS_TO_SHOW; ++i) {
+ Map.Entry<Integer, Float> label = sortedLabels.poll();
+ // ImageNet model prediction indices are 0-based.
+ singleImageResult.topKIndices.add(label.getKey());
+ singleImageResult.topKClasses.add(labelList.get(label.getKey()));
+ singleImageResult.topKProbs.add(label.getValue());
+ }
+ // Labels with lowest probability are returned first, hence need to reverse them.
+ Collections.reverse(singleImageResult.topKIndices);
+ Collections.reverse(singleImageResult.topKClasses);
+ Collections.reverse(singleImageResult.topKProbs);
+ return singleImageResult;
+ }
+}
--- /dev/null
+/*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
+
+ https://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 org.tensorflow.ovic;
+
+import java.util.ArrayList;
+
+/** Result class for inference run on a single image. */
+public class OvicSingleImageResult {
+
+ /** Top K classes and probabilities. */
+ public ArrayList<String> topKClasses;
+ public ArrayList<Float> topKProbs;
+ public ArrayList<Integer> topKIndices;
+
+ /** Latency (ms). */
+ public Long latency;
+
+ OvicSingleImageResult() {
+ topKClasses = new ArrayList<>();
+ topKProbs = new ArrayList<>();
+ topKIndices = new ArrayList<>();
+ latency = -1L;
+ }
+
+ @Override
+ public String toString() {
+ String textToShow = latency + "ms";
+ for (int k = 0; k < topKProbs.size(); ++k) {
+ textToShow +=
+ "\nPrediction ["
+ + k
+ + "] = Class "
+ + Integer.toString(topKIndices.get(k))
+ + " ("
+ + topKClasses.get(k)
+ + ") : "
+ + Float.toString(topKProbs.get(k));
+ }
+ return textToShow;
+ }
+
+}
--- /dev/null
+/*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
+
+ https://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 org.tensorflow.ovic;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Paths;
+import javax.imageio.ImageIO;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link org.tensorflow.ovic.OvicClassifier}. */
+@RunWith(JUnit4.class)
+public final class OvicClassifierTest {
+
+ private OvicClassifier classifier;
+ private InputStream labelsInputStream = null;
+ private MappedByteBuffer quantizedModel = null;
+ private MappedByteBuffer floatModel = null;
+ private MappedByteBuffer lowResModel = null;
+ private ByteBuffer testImage = null;
+ private ByteBuffer lowResTestImage = null;
+ private OvicSingleImageResult testResult = null;
+ private static final String LABELS_PATH = "testdata/labels.txt";
+ private static final String QUANTIZED_MODEL_PATH = "testdata/quantized_model.lite";
+ private static final String LOW_RES_MODEL_PATH = "testdata/low_res_model.lite";
+ private static final String FLOAT_MODEL_PATH = "testdata/float_model.lite";
+ private static final String TEST_IMAGE_PATH = "testdata/test_image_224.jpg";
+ private static final String TEST_LOW_RES_IMAGE_PATH = "testdata/test_image_128.jpg";
+ private static final int TEST_IMAGE_GROUNDTRUTH = 653; // "military uniform"
+
+ @Before
+ public void setUp() {
+ try {
+ File labelsfile = new File(getTestDir(LABELS_PATH));
+ labelsInputStream = new FileInputStream(labelsfile);
+ quantizedModel = loadModelFile(getTestDir(QUANTIZED_MODEL_PATH));
+ floatModel = loadModelFile(getTestDir(FLOAT_MODEL_PATH));
+ lowResModel = loadModelFile(getTestDir(LOW_RES_MODEL_PATH));
+ File imageFile = new File(getTestDir(TEST_IMAGE_PATH));
+ BufferedImage img = ImageIO.read(imageFile);
+ testImage = toByteBuffer(img);
+ // Low res image and models.
+ imageFile = new File(getTestDir(TEST_LOW_RES_IMAGE_PATH));
+ img = ImageIO.read(imageFile);
+ lowResTestImage = toByteBuffer(img);
+ } catch (IOException e) {
+ System.out.print(e.getMessage());
+ }
+ System.out.println("Successful setup");
+ }
+
+ private static String getTestDir(String testfile) throws IOException {
+ return Paths.get("third_party/tensorflow/contrib/lite/java/ovic/src/", testfile).toString();
+ }
+
+ @Test
+ public void ovicClassifier_quantizedModelCreateSuccess() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, quantizedModel);
+ assertThat(classifier != null).isTrue();
+ }
+
+ @Test
+ public void ovicClassifier_floatModelCreateSuccess() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, floatModel);
+ assertThat(classifier != null).isTrue();
+ }
+
+ @Test
+ public void ovicClassifier_quantizedModelClassifySuccess() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, quantizedModel);
+ testResult = classifier.classifyByteBuffer(testImage);
+ assertCorrectTopK(testResult);
+ }
+
+ @Test
+ public void ovicClassifier_floatModelClassifySuccess() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, floatModel);
+ testResult = classifier.classifyByteBuffer(testImage);
+ assertCorrectTopK(testResult);
+ }
+
+ @Test
+ public void ovicClassifier_lowResModelClassifySuccess() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, lowResModel);
+ testResult = classifier.classifyByteBuffer(lowResTestImage);
+ assertCorrectTopK(testResult);
+ }
+
+ @Test
+ public void ovicClassifier_latencyNotNull() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, floatModel);
+ testResult = classifier.classifyByteBuffer(testImage);
+ assertThat(testResult.latency != null).isTrue();
+ }
+
+ @Test
+ public void ovicClassifier_mismatchedInputResolutionFails() throws Exception {
+ classifier = new OvicClassifier(labelsInputStream, lowResModel);
+ int[] inputDims = classifier.getInputDims();
+ assertThat((inputDims[1] == 128) && (inputDims[2] == 128)).isTrue();
+ try {
+ testResult = classifier.classifyByteBuffer(testImage);
+ fail();
+ } catch (RuntimeException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "Failed to get input dimensions. 0-th input should have 49152 bytes, "
+ + "but found 150528 bytes.");
+ }
+ }
+
+ private static ByteBuffer toByteBuffer(BufferedImage image) {
+ ByteBuffer imgData = ByteBuffer.allocateDirect(
+ image.getHeight() * image.getWidth() * 3);
+ imgData.order(ByteOrder.nativeOrder());
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ int val = image.getRGB(x, y);
+ imgData.put((byte) ((val >> 16) & 0xFF));
+ imgData.put((byte) ((val >> 8) & 0xFF));
+ imgData.put((byte) (val & 0xFF));
+ }
+ }
+ return imgData;
+ }
+
+ private static void assertCorrectTopK(OvicSingleImageResult testResult) {
+ assertThat(testResult.topKClasses.size() > 0).isTrue();
+ Boolean topKAccurate = false;
+ // Assert that the correct class is in the top K.
+ for (int i = 0; i < testResult.topKIndices.size(); i++) {
+ if (testResult.topKIndices.get(i) == TEST_IMAGE_GROUNDTRUTH) {
+ topKAccurate = true;
+ break;
+ }
+ }
+ System.out.println(testResult.toString());
+ System.out.flush();
+ assertThat(topKAccurate).isTrue();
+ }
+
+ private static MappedByteBuffer loadModelFile(String modelFilePath) throws IOException {
+ File modelfile = new File(modelFilePath);
+ FileInputStream inputStream = new FileInputStream(modelfile);
+ FileChannel fileChannel = inputStream.getChannel();
+ long startOffset = 0L;
+ long declaredLength = fileChannel.size();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
+ }
+}
--- /dev/null
+background
+tench
+goldfish
+great white shark
+tiger shark
+hammerhead
+electric ray
+stingray
+cock
+hen
+ostrich
+brambling
+goldfinch
+house finch
+junco
+indigo bunting
+robin
+bulbul
+jay
+magpie
+chickadee
+water ouzel
+kite
+bald eagle
+vulture
+great grey owl
+European fire salamander
+common newt
+eft
+spotted salamander
+axolotl
+bullfrog
+tree frog
+tailed frog
+loggerhead
+leatherback turtle
+mud turtle
+terrapin
+box turtle
+banded gecko
+common iguana
+American chameleon
+whiptail
+agama
+frilled lizard
+alligator lizard
+Gila monster
+green lizard
+African chameleon
+Komodo dragon
+African crocodile
+American alligator
+triceratops
+thunder snake
+ringneck snake
+hognose snake
+green snake
+king snake
+garter snake
+water snake
+vine snake
+night snake
+boa constrictor
+rock python
+Indian cobra
+green mamba
+sea snake
+horned viper
+diamondback
+sidewinder
+trilobite
+harvestman
+scorpion
+black and gold garden spider
+barn spider
+garden spider
+black widow
+tarantula
+wolf spider
+tick
+centipede
+black grouse
+ptarmigan
+ruffed grouse
+prairie chicken
+peacock
+quail
+partridge
+African grey
+macaw
+sulphur-crested cockatoo
+lorikeet
+coucal
+bee eater
+hornbill
+hummingbird
+jacamar
+toucan
+drake
+red-breasted merganser
+goose
+black swan
+tusker
+echidna
+platypus
+wallaby
+koala
+wombat
+jellyfish
+sea anemone
+brain coral
+flatworm
+nematode
+conch
+snail
+slug
+sea slug
+chiton
+chambered nautilus
+Dungeness crab
+rock crab
+fiddler crab
+king crab
+American lobster
+spiny lobster
+crayfish
+hermit crab
+isopod
+white stork
+black stork
+spoonbill
+flamingo
+little blue heron
+American egret
+bittern
+crane
+limpkin
+European gallinule
+American coot
+bustard
+ruddy turnstone
+red-backed sandpiper
+redshank
+dowitcher
+oystercatcher
+pelican
+king penguin
+albatross
+grey whale
+killer whale
+dugong
+sea lion
+Chihuahua
+Japanese spaniel
+Maltese dog
+Pekinese
+Shih-Tzu
+Blenheim spaniel
+papillon
+toy terrier
+Rhodesian ridgeback
+Afghan hound
+basset
+beagle
+bloodhound
+bluetick
+black-and-tan coonhound
+Walker hound
+English foxhound
+redbone
+borzoi
+Irish wolfhound
+Italian greyhound
+whippet
+Ibizan hound
+Norwegian elkhound
+otterhound
+Saluki
+Scottish deerhound
+Weimaraner
+Staffordshire bullterrier
+American Staffordshire terrier
+Bedlington terrier
+Border terrier
+Kerry blue terrier
+Irish terrier
+Norfolk terrier
+Norwich terrier
+Yorkshire terrier
+wire-haired fox terrier
+Lakeland terrier
+Sealyham terrier
+Airedale
+cairn
+Australian terrier
+Dandie Dinmont
+Boston bull
+miniature schnauzer
+giant schnauzer
+standard schnauzer
+Scotch terrier
+Tibetan terrier
+silky terrier
+soft-coated wheaten terrier
+West Highland white terrier
+Lhasa
+flat-coated retriever
+curly-coated retriever
+golden retriever
+Labrador retriever
+Chesapeake Bay retriever
+German short-haired pointer
+vizsla
+English setter
+Irish setter
+Gordon setter
+Brittany spaniel
+clumber
+English springer
+Welsh springer spaniel
+cocker spaniel
+Sussex spaniel
+Irish water spaniel
+kuvasz
+schipperke
+groenendael
+malinois
+briard
+kelpie
+komondor
+Old English sheepdog
+Shetland sheepdog
+collie
+Border collie
+Bouvier des Flandres
+Rottweiler
+German shepherd
+Doberman
+miniature pinscher
+Greater Swiss Mountain dog
+Bernese mountain dog
+Appenzeller
+EntleBucher
+boxer
+bull mastiff
+Tibetan mastiff
+French bulldog
+Great Dane
+Saint Bernard
+Eskimo dog
+malamute
+Siberian husky
+dalmatian
+affenpinscher
+basenji
+pug
+Leonberg
+Newfoundland
+Great Pyrenees
+Samoyed
+Pomeranian
+chow
+keeshond
+Brabancon griffon
+Pembroke
+Cardigan
+toy poodle
+miniature poodle
+standard poodle
+Mexican hairless
+timber wolf
+white wolf
+red wolf
+coyote
+dingo
+dhole
+African hunting dog
+hyena
+red fox
+kit fox
+Arctic fox
+grey fox
+tabby
+tiger cat
+Persian cat
+Siamese cat
+Egyptian cat
+cougar
+lynx
+leopard
+snow leopard
+jaguar
+lion
+tiger
+cheetah
+brown bear
+American black bear
+ice bear
+sloth bear
+mongoose
+meerkat
+tiger beetle
+ladybug
+ground beetle
+long-horned beetle
+leaf beetle
+dung beetle
+rhinoceros beetle
+weevil
+fly
+bee
+ant
+grasshopper
+cricket
+walking stick
+cockroach
+mantis
+cicada
+leafhopper
+lacewing
+dragonfly
+damselfly
+admiral
+ringlet
+monarch
+cabbage butterfly
+sulphur butterfly
+lycaenid
+starfish
+sea urchin
+sea cucumber
+wood rabbit
+hare
+Angora
+hamster
+porcupine
+fox squirrel
+marmot
+beaver
+guinea pig
+sorrel
+zebra
+hog
+wild boar
+warthog
+hippopotamus
+ox
+water buffalo
+bison
+ram
+bighorn
+ibex
+hartebeest
+impala
+gazelle
+Arabian camel
+llama
+weasel
+mink
+polecat
+black-footed ferret
+otter
+skunk
+badger
+armadillo
+three-toed sloth
+orangutan
+gorilla
+chimpanzee
+gibbon
+siamang
+guenon
+patas
+baboon
+macaque
+langur
+colobus
+proboscis monkey
+marmoset
+capuchin
+howler monkey
+titi
+spider monkey
+squirrel monkey
+Madagascar cat
+indri
+Indian elephant
+African elephant
+lesser panda
+giant panda
+barracouta
+eel
+coho
+rock beauty
+anemone fish
+sturgeon
+gar
+lionfish
+puffer
+abacus
+abaya
+academic gown
+accordion
+acoustic guitar
+aircraft carrier
+airliner
+airship
+altar
+ambulance
+amphibian
+analog clock
+apiary
+apron
+ashcan
+assault rifle
+backpack
+bakery
+balance beam
+balloon
+ballpoint
+Band Aid
+banjo
+bannister
+barbell
+barber chair
+barbershop
+barn
+barometer
+barrel
+barrow
+baseball
+basketball
+bassinet
+bassoon
+bathing cap
+bath towel
+bathtub
+beach wagon
+beacon
+beaker
+bearskin
+beer bottle
+beer glass
+bell cote
+bib
+bicycle-built-for-two
+bikini
+binder
+binoculars
+birdhouse
+boathouse
+bobsled
+bolo tie
+bonnet
+bookcase
+bookshop
+bottlecap
+bow
+bow tie
+brass
+brassiere
+breakwater
+breastplate
+broom
+bucket
+buckle
+bulletproof vest
+bullet train
+butcher shop
+cab
+caldron
+candle
+cannon
+canoe
+can opener
+cardigan
+car mirror
+carousel
+carpenter's kit
+carton
+car wheel
+cash machine
+cassette
+cassette player
+castle
+catamaran
+CD player
+cello
+cellular telephone
+chain
+chainlink fence
+chain mail
+chain saw
+chest
+chiffonier
+chime
+china cabinet
+Christmas stocking
+church
+cinema
+cleaver
+cliff dwelling
+cloak
+clog
+cocktail shaker
+coffee mug
+coffeepot
+coil
+combination lock
+computer keyboard
+confectionery
+container ship
+convertible
+corkscrew
+cornet
+cowboy boot
+cowboy hat
+cradle
+crane
+crash helmet
+crate
+crib
+Crock Pot
+croquet ball
+crutch
+cuirass
+dam
+desk
+desktop computer
+dial telephone
+diaper
+digital clock
+digital watch
+dining table
+dishrag
+dishwasher
+disk brake
+dock
+dogsled
+dome
+doormat
+drilling platform
+drum
+drumstick
+dumbbell
+Dutch oven
+electric fan
+electric guitar
+electric locomotive
+entertainment center
+envelope
+espresso maker
+face powder
+feather boa
+file
+fireboat
+fire engine
+fire screen
+flagpole
+flute
+folding chair
+football helmet
+forklift
+fountain
+fountain pen
+four-poster
+freight car
+French horn
+frying pan
+fur coat
+garbage truck
+gasmask
+gas pump
+goblet
+go-kart
+golf ball
+golfcart
+gondola
+gong
+gown
+grand piano
+greenhouse
+grille
+grocery store
+guillotine
+hair slide
+hair spray
+half track
+hammer
+hamper
+hand blower
+hand-held computer
+handkerchief
+hard disc
+harmonica
+harp
+harvester
+hatchet
+holster
+home theater
+honeycomb
+hook
+hoopskirt
+horizontal bar
+horse cart
+hourglass
+iPod
+iron
+jack-o'-lantern
+jean
+jeep
+jersey
+jigsaw puzzle
+jinrikisha
+joystick
+kimono
+knee pad
+knot
+lab coat
+ladle
+lampshade
+laptop
+lawn mower
+lens cap
+letter opener
+library
+lifeboat
+lighter
+limousine
+liner
+lipstick
+Loafer
+lotion
+loudspeaker
+loupe
+lumbermill
+magnetic compass
+mailbag
+mailbox
+maillot
+maillot
+manhole cover
+maraca
+marimba
+mask
+matchstick
+maypole
+maze
+measuring cup
+medicine chest
+megalith
+microphone
+microwave
+military uniform
+milk can
+minibus
+miniskirt
+minivan
+missile
+mitten
+mixing bowl
+mobile home
+Model T
+modem
+monastery
+monitor
+moped
+mortar
+mortarboard
+mosque
+mosquito net
+motor scooter
+mountain bike
+mountain tent
+mouse
+mousetrap
+moving van
+muzzle
+nail
+neck brace
+necklace
+nipple
+notebook
+obelisk
+oboe
+ocarina
+odometer
+oil filter
+organ
+oscilloscope
+overskirt
+oxcart
+oxygen mask
+packet
+paddle
+paddlewheel
+padlock
+paintbrush
+pajama
+palace
+panpipe
+paper towel
+parachute
+parallel bars
+park bench
+parking meter
+passenger car
+patio
+pay-phone
+pedestal
+pencil box
+pencil sharpener
+perfume
+Petri dish
+photocopier
+pick
+pickelhaube
+picket fence
+pickup
+pier
+piggy bank
+pill bottle
+pillow
+ping-pong ball
+pinwheel
+pirate
+pitcher
+plane
+planetarium
+plastic bag
+plate rack
+plow
+plunger
+Polaroid camera
+pole
+police van
+poncho
+pool table
+pop bottle
+pot
+potter's wheel
+power drill
+prayer rug
+printer
+prison
+projectile
+projector
+puck
+punching bag
+purse
+quill
+quilt
+racer
+racket
+radiator
+radio
+radio telescope
+rain barrel
+recreational vehicle
+reel
+reflex camera
+refrigerator
+remote control
+restaurant
+revolver
+rifle
+rocking chair
+rotisserie
+rubber eraser
+rugby ball
+rule
+running shoe
+safe
+safety pin
+saltshaker
+sandal
+sarong
+sax
+scabbard
+scale
+school bus
+schooner
+scoreboard
+screen
+screw
+screwdriver
+seat belt
+sewing machine
+shield
+shoe shop
+shoji
+shopping basket
+shopping cart
+shovel
+shower cap
+shower curtain
+ski
+ski mask
+sleeping bag
+slide rule
+sliding door
+slot
+snorkel
+snowmobile
+snowplow
+soap dispenser
+soccer ball
+sock
+solar dish
+sombrero
+soup bowl
+space bar
+space heater
+space shuttle
+spatula
+speedboat
+spider web
+spindle
+sports car
+spotlight
+stage
+steam locomotive
+steel arch bridge
+steel drum
+stethoscope
+stole
+stone wall
+stopwatch
+stove
+strainer
+streetcar
+stretcher
+studio couch
+stupa
+submarine
+suit
+sundial
+sunglass
+sunglasses
+sunscreen
+suspension bridge
+swab
+sweatshirt
+swimming trunks
+swing
+switch
+syringe
+table lamp
+tank
+tape player
+teapot
+teddy
+television
+tennis ball
+thatch
+theater curtain
+thimble
+thresher
+throne
+tile roof
+toaster
+tobacco shop
+toilet seat
+torch
+totem pole
+tow truck
+toyshop
+tractor
+trailer truck
+tray
+trench coat
+tricycle
+trimaran
+tripod
+triumphal arch
+trolleybus
+trombone
+tub
+turnstile
+typewriter keyboard
+umbrella
+unicycle
+upright
+vacuum
+vase
+vault
+velvet
+vending machine
+vestment
+viaduct
+violin
+volleyball
+waffle iron
+wall clock
+wallet
+wardrobe
+warplane
+washbasin
+washer
+water bottle
+water jug
+water tower
+whiskey jug
+whistle
+wig
+window screen
+window shade
+Windsor tie
+wine bottle
+wing
+wok
+wooden spoon
+wool
+worm fence
+wreck
+yawl
+yurt
+web site
+comic book
+crossword puzzle
+street sign
+traffic light
+book jacket
+menu
+plate
+guacamole
+consomme
+hot pot
+trifle
+ice cream
+ice lolly
+French loaf
+bagel
+pretzel
+cheeseburger
+hotdog
+mashed potato
+head cabbage
+broccoli
+cauliflower
+zucchini
+spaghetti squash
+acorn squash
+butternut squash
+cucumber
+artichoke
+bell pepper
+cardoon
+mushroom
+Granny Smith
+strawberry
+orange
+lemon
+fig
+pineapple
+banana
+jackfruit
+custard apple
+pomegranate
+hay
+carbonara
+chocolate sauce
+dough
+meat loaf
+pizza
+potpie
+burrito
+red wine
+espresso
+cup
+eggnog
+alp
+bubble
+cliff
+coral reef
+geyser
+lakeside
+promontory
+sandbar
+seashore
+valley
+volcano
+ballplayer
+groom
+scuba diver
+rapeseed
+daisy
+yellow lady's slipper
+corn
+acorn
+hip
+buckeye
+coral fungus
+agaric
+gyromitra
+stinkhorn
+earthstar
+hen-of-the-woods
+bolete
+ear
+toilet tissue