org.openimaj.image.dataset.BingImageDataset.java Source code

Java tutorial

Introduction

Here is the source code for org.openimaj.image.dataset.BingImageDataset.java

Source

/**
 * Copyright (c) 2011, The University of Southampton and the individual contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   *    Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *   *   Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *   *   Neither the name of the University of Southampton nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.openimaj.image.dataset;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.BackingStoreException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.openimaj.data.dataset.ReadableListDataset;
import org.openimaj.data.identity.Identifiable;
import org.openimaj.image.DisplayUtilities;
import org.openimaj.image.FImage;
import org.openimaj.image.Image;
import org.openimaj.image.ImageUtilities;
import org.openimaj.io.HttpUtils;
import org.openimaj.io.InputStreamObjectReader;
import org.openimaj.util.api.auth.DefaultTokenFactory;
import org.openimaj.util.api.auth.common.BingAPIToken;

/**
 * Image datasets dynamically created from the Bing search API.
 * 
 * <h5> WARNING </h5>
 * Some of the images inside this dataset may be set to {@code null}if they could not be loaded.
 *
 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
 *
 * @param <IMAGE>
 *            The type of {@link Image} instance held by the dataset.
 */
public class BingImageDataset<IMAGE extends Image<?, IMAGE>> extends ReadableListDataset<IMAGE, InputStream>
        implements Identifiable {
    public static class ImageDataSourceQuery {
        public static enum SafeSearch {
            Off, Moderate, Strict;
        }

        public static enum Aspect {
            Square, Wide, Tall, All;
        }

        public static enum Color {
            /**
             * Return color images
             */
            ColorOnly,
            /**
             * Return black and white images
             */
            Monochrome, Black, Blue, Brown, Gray, Green, Orange, Pink, Purple, Red, Teal, White, Yellow
        }

        public static enum Freshness {
            /**
             * Return images discovered within the last 24 hours
             */
            Day,
            /**
             * Return images discovered within the last 7 days
             */
            Week,
            /**
             * Return images discovered within the last 30 days
             */
            Month
        }

        /**
         * Filter images by content
         */
        public static enum ImageContent {
            /**
             * Return images that show only a person's face
             */
            Face,
            /**
             * Return images that show only a person's head and shoulders
             */
            Portrait
        }

        /**
         * Filter images by image type.
         */
        public static enum ImageType {
            /**
             * Return only animated GIFs
             */
            AnimatedGif,
            /**
             * Return only clip art images
             */
            Clipart,
            /**
             * Return only line drawings
             */
            Line,
            /**
             * Return only photographs (excluding line drawings, animated Gifs,
             * and clip art)
             */
            Photo,
            /**
             * Return only images that contain items where Bing knows of a
             * merchant that is selling the items.
             */
            Shopping,
        }

        public static enum License {
            /**
             * Return images where the creator has waived their exclusive
             * rights, to the fullest extent allowed by law.
             */
            Public,
            /**
             * Return images that may be shared with others. Changing or editing
             * the image might not be allowed. Also, modifying, sharing, and
             * using the image for commercial purposes might not be allowed.
             * Typically, this option returns the most images.
             */
            Share,
            /**
             * Return images that may be shared with others for personal or
             * commercial purposes. Changing or editing the image might not be
             * allowed.
             */
            ShareCommercially,
            /**
             * Return images that may be modified, shared, and used. Changing or
             * editing the image might not be allowed. Modifying, sharing, and
             * using the image for commercial purposes might not be allowed.
             */
            Modify,
            /**
             * Return images that may be modified, shared, and used for personal
             * or commercial purposes. Typically, this option returns the fewest
             * images.
             */
            ModifyCommercially,
            /**
             * Do not filter by license type. Specifying this value is the same
             * as not specifying the license parameter.
             */
            All
        }

        public static enum Size {
            /**
             * Return images that are less than 200x200 pixels
             */
            Small,
            /**
             * Return images that are greater than or equal to 200x200 pixels
             * but less than 500x500 pixels
             */
            Medium,
            /**
             * Return images that are 500x500 pixels or larger
             */
            Large,
            /**
             * Return wallpaper images.
             */
            Wallpaper,
            /**
             * Do not filter by size. Specifying this value is the same as not
             * specifying the size parameter.
             */
            All
        }

        SafeSearch safeSearch;
        Aspect aspect;
        Color color;
        Freshness freshness;
        int height;
        ImageContent imageContent;
        ImageType imageType;
        License license;
        Size size;
        int width;
        int offset;
        int count;
        String query;
        private String accountKey;

        /**
         * @return the safeSearch
         */
        public SafeSearch getSafeSearch() {
            return safeSearch;
        }

        /**
         * @param safeSearch
         *            the safeSearch to set
         */
        public void setSafeSearch(SafeSearch safeSearch) {
            this.safeSearch = safeSearch;
        }

        /**
         * @return the aspect
         */
        public Aspect getAspect() {
            return aspect;
        }

        /**
         * @param aspect
         *            the aspect to set
         */
        public void setAspect(Aspect aspect) {
            this.aspect = aspect;
        }

        /**
         * @return the color
         */
        public Color getColor() {
            return color;
        }

        /**
         * @param color
         *            the color to set
         */
        public void setColor(Color color) {
            this.color = color;
        }

        /**
         * @return the freshness
         */
        public Freshness getFreshness() {
            return freshness;
        }

        /**
         * @param freshness
         *            the freshness to set
         */
        public void setFreshness(Freshness freshness) {
            this.freshness = freshness;
        }

        /**
         * @return the height
         */
        public int getHeight() {
            return height;
        }

        /**
         * @param height
         *            the height to set
         */
        public void setHeight(int height) {
            this.height = height;
        }

        /**
         * @return the imageContent
         */
        public ImageContent getImageContent() {
            return imageContent;
        }

        /**
         * @param imageContent
         *            the imageContent to set
         */
        public void setImageContent(ImageContent imageContent) {
            this.imageContent = imageContent;
        }

        /**
         * @return the imageType
         */
        public ImageType getImageType() {
            return imageType;
        }

        /**
         * @param imageType
         *            the imageType to set
         */
        public void setImageType(ImageType imageType) {
            this.imageType = imageType;
        }

        /**
         * @return the license
         */
        public License getLicense() {
            return license;
        }

        /**
         * @param license
         *            the license to set
         */
        public void setLicense(License license) {
            this.license = license;
        }

        /**
         * @return the size
         */
        public Size getSize() {
            return size;
        }

        /**
         * @param size
         *            the size to set
         */
        public void setSize(Size size) {
            this.size = size;
        }

        /**
         * @return the width
         */
        public int getWidth() {
            return width;
        }

        /**
         * @param width
         *            the width to set
         */
        public void setWidth(int width) {
            this.width = width;
        }

        /**
         * @return the offset
         */
        public int getOffset() {
            return offset;
        }

        /**
         * @param offset
         *            the offset to set
         */
        public void setOffset(int offset) {
            this.offset = offset;
        }

        /**
         * @return the count
         */
        public int getCount() {
            return count;
        }

        /**
         * @param count
         *            the count to set
         */
        public void setCount(int count) {
            this.count = count;
        }

        /**
         * @return the query
         */
        public String getQuery() {
            return query;
        }

        /**
         * @param query
         *            the query to set
         */
        public void setQuery(String query) {
            this.query = query;
        }

        public void setSubscriptionKey(String accountKey) {
            this.accountKey = accountKey;
        }

        public URI buildURI() throws URISyntaxException {
            final URIBuilder builder = new URIBuilder(
                    "https://api.cognitive.microsoft.com/bing/v5.0/images/search");

            builder.setParameter("q", query);
            builder.setParameter("count", count + "");
            builder.setParameter("offset", offset + "");

            return builder.build();
        }

    }

    public static class ImageDataSourceResponse {
        String contentUrl;

        public ImageDataSourceResponse(JSONObject jro) {
            contentUrl = (String) jro.get("contentUrl");
        }

        public String getContentUrl() {
            return contentUrl;
        }
    }

    List<ImageDataSourceResponse> images;
    ImageDataSourceQuery query;

    protected BingImageDataset(InputStreamObjectReader<IMAGE> reader, List<ImageDataSourceResponse> results,
            ImageDataSourceQuery query) {
        super(reader);
        this.images = results;
        this.query = query;
    }

    @Override
    public IMAGE getInstance(int index) {
        return read(getImage(index));
    }

    /**
     * Loads the image in {@code next} and converts it to the type {@code <IMAGE>}
     * @param next the image source to load the image from
     * @return the loaded and converted image if loading the image worked,
     *         {@code null} otherwise
     */
    private IMAGE read(ImageDataSourceResponse next) {
        if (next == null)
            return null;

        final String imageURL = next.getContentUrl();

        InputStream stream = null;
        try {
            stream = HttpUtils.readURL(new URL(imageURL));

            return reader.read(stream);
        } catch (final MalformedURLException e) {
            //if the URL is malformed, something went wrong with programming
            throw new RuntimeException(e);
        } catch (final IOException e) {
            if (e.getCause() instanceof org.apache.sanselan.ImageReadException) {
                // image urls that redirect to html pages will have this error (eg tinypic.com)
                System.out.println("The following URL didn't redirect to an image: " + imageURL);
            } else {
                // there was some issue with loading data from the URL
                e.printStackTrace();
            }
            return null;
        } finally {
            try {
                if (stream != null)
                    stream.close();
            } catch (final IOException e) {
                // ignore
            }
        }
    }

    @Override
    public int numInstances() {
        return images.size();
    }

    /**
     * Get the underlying {@link ImageDataSourceResponse} objects that back the
     * dataset.
     *
     * @return the underlying {@link ImageDataSourceResponse} objects
     */
    public List<ImageDataSourceResponse> getImages() {
        return images;
    }

    /**
     * Get the specific underlying {@link ImageDataSourceResponse} for the given
     * index.
     *
     * @param index
     *            the index
     * @return the specific {@link ImageDataSourceResponse} for the given index.
     */
    public ImageDataSourceResponse getImage(int index) {
        return images.get(index);
    }

    private static List<ImageDataSourceResponse> performSinglePageQuery(ImageDataSourceQuery query) {
        final HttpClient httpclient = HttpClients.createDefault();

        try {
            final URI uri = query.buildURI();
            final HttpGet request = new HttpGet(uri);
            request.setHeader("Ocp-Apim-Subscription-Key", query.accountKey);

            final HttpResponse response = httpclient.execute(request);

            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                throw new IOException(
                        "HTTP ERROR 401: Unauthorized Recieved. " + "You probably have the incorrect API Key");
            }
            final HttpEntity entity = response.getEntity();

            if (entity != null) {
                try {
                    final JSONParser parser = new JSONParser();
                    final JSONObject o = (JSONObject) parser.parse(EntityUtils.toString(entity));

                    final JSONArray jresults = ((JSONArray) o.get("value"));
                    final List<ImageDataSourceResponse> results = new ArrayList<>(jresults.size());

                    for (final Object jro : jresults) {
                        results.add(new ImageDataSourceResponse((JSONObject) jro));
                    }

                    return results;
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (final IOException e) {
            e.printStackTrace();
        } catch (final URISyntaxException e) {
            e.printStackTrace();
        }

        return null;
    }

    private static List<ImageDataSourceResponse> performQuery(ImageDataSourceQuery query, int number) {
        if (number <= 0)
            number = 1000;

        query.setOffset(0);
        query.setCount(50);

        final List<ImageDataSourceResponse> images = new ArrayList<ImageDataSourceResponse>();
        for (int i = 0; i < 20; i++) {
            final List<ImageDataSourceResponse> res = performSinglePageQuery(query);

            if (res == null || res.size() == 0)
                break;

            images.addAll(res);

            if (images.size() >= number)
                break;

            query.setOffset(query.getOffset() + 50);
        }

        if (images.size() <= number)
            return images;
        return images.subList(0, number);
    }

    /**
     * Perform a search with the given query. The appid must have been set
     * externally.
     *
     *
     * @param reader
     *            the reader with which to load the images
     * @param query
     *            the query
     * @param number
     *            the target number of results; the resultant dataset may
     *            contain fewer images than specified.
     * @return a new {@link BingImageDataset} created from the query.
     */
    public static <IMAGE extends Image<?, IMAGE>> BingImageDataset<IMAGE> create(
            InputStreamObjectReader<IMAGE> reader, ImageDataSourceQuery query, int number) {
        return new BingImageDataset<IMAGE>(reader, performQuery(query, number), query);
    }

    /**
     * Perform a search with the given query. The given api token will be used
     * to set the appid in the query object.
     *
     * @param reader
     *            the reader with which to load the images
     * @param token
     *            the api authentication token
     * @param query
     *            the query
     * @param number
     *            the target number of results; the resultant dataset may
     *            contain fewer images than specified.
     * @return a new {@link BingImageDataset} created from the query.
     */
    public static <IMAGE extends Image<?, IMAGE>> BingImageDataset<IMAGE> create(
            InputStreamObjectReader<IMAGE> reader, BingAPIToken token, ImageDataSourceQuery query, int number) {
        query.setSubscriptionKey(token.accountKey);
        return new BingImageDataset<IMAGE>(reader, performQuery(query, number), query);
    }

    /**
     * Perform a search with the given query string.
     *
     * @param reader
     *            the reader with which to load the images
     * @param token
     *            the api authentication token
     * @param query
     *            the query
     * @param number
     *            the target number of results; the resultant dataset may
     *            contain fewer images than specified.
     * @return a new {@link BingImageDataset} created from the query.
     */
    public static <IMAGE extends Image<?, IMAGE>> BingImageDataset<IMAGE> create(
            InputStreamObjectReader<IMAGE> reader, BingAPIToken token, String query, int number) {
        final ImageDataSourceQuery aq = new ImageDataSourceQuery();
        aq.setSubscriptionKey(token.accountKey);
        aq.setQuery(query);

        return new BingImageDataset<IMAGE>(reader, performQuery(aq, number), aq);
    }

    @Override
    public String getID() {
        return query.getQuery();
    }

    public static void main(String[] args) throws BackingStoreException {
        final BingAPIToken apiToken = DefaultTokenFactory.get(BingAPIToken.class);
        final BingImageDataset<FImage> ds = BingImageDataset.create(ImageUtilities.FIMAGE_READER, apiToken, "foo",
                10);

        DisplayUtilities.display(ds.getRandomInstance());
    }
}