001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.annotation.evaluation.datasets;
031
032import java.io.File;
033import java.io.IOException;
034import java.io.InputStream;
035import java.net.URL;
036
037import org.apache.commons.io.FileUtils;
038import org.apache.commons.vfs2.FileObject;
039import org.apache.commons.vfs2.FileSystemException;
040import org.apache.commons.vfs2.FileSystemManager;
041import org.apache.commons.vfs2.VFS;
042import org.openimaj.data.DataUtils;
043import org.openimaj.data.dataset.VFSGroupDataset;
044import org.openimaj.data.dataset.VFSListDataset;
045import org.openimaj.data.identity.Identifiable;
046import org.openimaj.experiment.annotations.DatasetDescription;
047import org.openimaj.image.Image;
048import org.openimaj.image.ImageProvider;
049import org.openimaj.image.ImageUtilities;
050import org.openimaj.io.InputStreamObjectReader;
051import org.openimaj.io.ObjectReader;
052import org.openimaj.math.geometry.point.Point2dImpl;
053import org.openimaj.math.geometry.shape.Polygon;
054import org.openimaj.math.geometry.shape.Rectangle;
055
056import com.jmatio.io.MatFileReader;
057import com.jmatio.types.MLDouble;
058
059/**
060 * The CalTech101 image dataset. Contains 102 classes of image (101 objects +
061 * background), and (for most images) outlines and bounding boxes of the object.
062 * Images are approximately 300x200 pixels in size.
063 * 
064 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
065 */
066@DatasetDescription(
067                name = "CalTech101",
068                description = "Pictures of objects belonging to 101 categories. " +
069                                "About 40 to 800 images per category. Most categories have about " +
070                                "50 images. The size of each image is roughly 300 x 200 pixels.",
071                creator = "Fei-Fei Li, Marco Andreetto, and Marc 'Aurelio Ranzato",
072                url = "http://www.vision.caltech.edu/Image_Datasets/Caltech101/",
073                downloadUrls = {
074                                "http://datasets.openimaj.org/Caltech101/101_ObjectCategories.zip",
075                                "http://datasets.openimaj.org/Caltech101/Annotations.zip"
076                })
077public class Caltech101 {
078        private static final String IMAGES_ZIP = "Caltech101/101_ObjectCategories.zip";
079        private static final String IMAGES_DOWNLOAD_URL = "http://datasets.openimaj.org/Caltech101/101_ObjectCategories.zip";
080        private static final String ANNOTATIONS_ZIP = "Caltech101/Annotations.zip";
081        private static final String ANNOTATIONS_DOWNLOAD_URL = "http://datasets.openimaj.org/Caltech101/Annotations.zip";
082
083        private Caltech101() {
084        }
085
086        /**
087         * Get a dataset of the Caltech 101 images. If the dataset hasn't been
088         * downloaded, it will be fetched automatically and stored in the OpenIMAJ
089         * data directory. The images in the dataset are grouped by their class.
090         * 
091         * @see DataUtils#getDataDirectory()
092         * 
093         * @param reader
094         * @return a dataset of images
095         * @throws IOException
096         *             if a problem occurs loading the dataset
097         */
098        public static <IMAGE extends Image<?, IMAGE>> VFSGroupDataset<IMAGE> getImages(InputStreamObjectReader<IMAGE> reader)
099                        throws IOException
100        {
101                return new VFSGroupDataset<IMAGE>(downloadAndGetImagePath(), reader);
102        }
103
104        private static String downloadAndGetImagePath() throws IOException {
105                final File dataset = DataUtils.getDataLocation(IMAGES_ZIP);
106
107                if (!(dataset.exists())) {
108                        dataset.getParentFile().mkdirs();
109                        FileUtils.copyURLToFile(new URL(IMAGES_DOWNLOAD_URL), dataset);
110                }
111
112                return "zip:file:" + dataset.toString() + "!101_ObjectCategories/";
113        }
114
115        private static String downloadAndGetAnnotationPath() throws IOException {
116                final File dataset = DataUtils.getDataLocation(ANNOTATIONS_ZIP);
117
118                if (!(dataset.exists())) {
119                        dataset.getParentFile().mkdirs();
120                        FileUtils.copyURLToFile(new URL(ANNOTATIONS_DOWNLOAD_URL), dataset);
121                }
122
123                return "zip:file:" + dataset.toString() + "!Annotations/";
124        }
125
126        /**
127         * A record in the Caltech 101 dataset. Contains the image together with
128         * (optional) metadata on the bounds of the object in the image as well as
129         * the class of object in the image.
130         * 
131         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
132         * 
133         * @param <IMAGE>
134         *            The type of image that is loaded
135         */
136        public static abstract class Record<IMAGE extends Image<?, IMAGE>> implements Identifiable, ImageProvider<IMAGE> {
137                private Rectangle bounds;
138                private Polygon contour;
139                private String id;
140                private String objectClass;
141
142                protected Record(FileObject image) throws FileSystemException, IOException {
143                        final FileSystemManager fsManager = VFS.getManager();
144                        final FileObject imagesBase = fsManager.resolveFile(downloadAndGetImagePath());
145                        final FileObject annotationsBase = fsManager.resolveFile(downloadAndGetAnnotationPath());
146
147                        // get the id
148                        id = imagesBase.getName().getRelativeName(image.getName());
149
150                        // the class
151                        objectClass = image.getParent().getName().getBaseName();
152
153                        // find the annotation file
154                        final String annotationFileName = id.replace("image_", "annotation_").replace(".jpg", ".mat");
155                        final FileObject annotationFile = annotationsBase.resolveFile(annotationFileName);
156                        parseAnnotations(annotationFile);
157                }
158
159                private void parseAnnotations(FileObject annotationFile) throws IOException {
160                        if (!annotationFile.exists()) {
161                                return;
162                        }
163
164                        final MatFileReader reader = new MatFileReader(annotationFile.getContent().getInputStream());
165
166                        final MLDouble boxes = (MLDouble) reader.getMLArray("box_coord");
167                        this.bounds = new Rectangle(
168                                        (float) (double) boxes.getReal(2) - 1,
169                                        (float) (double) boxes.getReal(0) - 1,
170                                        (float) (boxes.getReal(3) - boxes.getReal(2)) - 1,
171                                        (float) (boxes.getReal(1) - boxes.getReal(0)) - 1);
172
173                        final double[][] contourData = ((MLDouble) reader.getMLArray("obj_contour")).getArray();
174                        this.contour = new Polygon();
175                        for (int i = 0; i < contourData[0].length; i++) {
176                                contour.points.add(
177                                                new Point2dImpl((float) contourData[0][i] + bounds.x - 1,
178                                                                (float) contourData[1][i] + bounds.y - 1)
179                                                );
180                        }
181                        contour.close();
182                }
183
184                @Override
185                public String getID() {
186                        return id;
187                }
188
189                /**
190                 * Get the bounds rectangle if it is available
191                 * 
192                 * @return the bounds
193                 */
194                public Rectangle getBounds() {
195                        return bounds;
196                }
197
198                /**
199                 * Get the object polygon if it is available.
200                 * 
201                 * @return the contour
202                 */
203                public Polygon getContour() {
204                        return contour;
205                }
206
207                /**
208                 * Get the class of the object depicted in the image.
209                 * 
210                 * @return the class
211                 */
212                public String getObjectClass() {
213                        return objectClass;
214                }
215        }
216
217        /**
218         * An {@link ObjectReader} for {@link Record}s.
219         * 
220         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
221         * 
222         * @param <IMAGE>
223         *            Type of image being read
224         */
225        private static class RecordReader<IMAGE extends Image<?, IMAGE>> implements ObjectReader<Record<IMAGE>, FileObject> {
226                private VFSListDataset.FileObjectISReader<IMAGE> imageReader;
227
228                public RecordReader(InputStreamObjectReader<IMAGE> reader) {
229                        this.imageReader = new VFSListDataset.FileObjectISReader<IMAGE>(reader);
230                }
231
232                @Override
233                public Record<IMAGE> read(final FileObject source) throws IOException {
234                        return new Record<IMAGE>(source) {
235
236                                @Override
237                                public IMAGE getImage() {
238                                        try {
239                                                return imageReader.read(source);
240                                        } catch (final IOException e) {
241                                                throw new RuntimeException(e);
242                                        }
243                                }
244                        };
245                }
246
247                @Override
248                public boolean canRead(FileObject source, String name) {
249                        InputStream stream = null;
250                        try {
251                                stream = source.getContent().getInputStream();
252
253                                return ImageUtilities.FIMAGE_READER.canRead(stream, source.getName().getBaseName());
254                        } catch (final FileSystemException e) {
255                                return false;
256                        } finally {
257                                if (stream != null) {
258                                        try {
259                                                stream.close();
260                                        } catch (final IOException e) {
261                                        }
262                                }
263                        }
264                }
265        }
266
267        /**
268         * Get a dataset of the Caltech 101 images and metadata. If the dataset
269         * hasn't been downloaded, it will be fetched automatically and stored in
270         * the OpenIMAJ data directory. The images in the dataset are grouped by
271         * their class.
272         * 
273         * @see DataUtils#getDataDirectory()
274         * 
275         * @param reader
276         *            a reader for reading images (usually a
277         *            {@link ImageUtilities#FIMAGE_READER} or
278         *            {@link ImageUtilities#MBFIMAGE_READER}).
279         * @return a dataset of images and metadate
280         * @throws IOException
281         *             if a problem occurs loading the dataset
282         */
283        public static <IMAGE extends Image<?, IMAGE>> VFSGroupDataset<Record<IMAGE>> getData(
284                        InputStreamObjectReader<IMAGE> reader) throws IOException
285        {
286                return new VFSGroupDataset<Record<IMAGE>>(downloadAndGetImagePath(), new RecordReader<IMAGE>(reader));
287        }
288}