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.feature;
031
032import java.io.File;
033import java.io.IOException;
034
035import org.apache.log4j.Logger;
036import org.openimaj.data.identity.Identifiable;
037import org.openimaj.io.IOUtils;
038import org.openimaj.io.WriteableBinary;
039
040/**
041 * A simple wrapper for a feature extractor that caches the extracted feature to
042 * disk. If a feature has already been generated for a given object, it will be
043 * re-read from disk rather than being re-generated.
044 *
045 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
046 *
047 * @param <FEATURE>
048 *            Type of feature
049 * @param <OBJECT>
050 *            Type of object
051 */
052public class DiskCachingFeatureExtractor<FEATURE, OBJECT extends Identifiable>
053                implements
054                FeatureExtractor<FEATURE, OBJECT>
055{
056        private static Logger logger = Logger.getLogger(DiskCachingFeatureExtractor.class);
057
058        private File cacheDir;
059        private FeatureExtractor<FEATURE, OBJECT> extractor;
060        private boolean force;
061
062        /**
063         * Construct the cache in the given directory. There will be one file
064         * created per object. The given extractor will be used to generate the
065         * features.
066         *
067         * @param cacheDir
068         *            the location of the cache
069         * @param extractor
070         *            the feature extractor
071         */
072        public DiskCachingFeatureExtractor(File cacheDir, FeatureExtractor<FEATURE, OBJECT> extractor) {
073                this(cacheDir, extractor, false);
074        }
075
076        /**
077         * Construct the cache in the given directory. There will be one file
078         * created per object. The given extractor will be used to generate the
079         * features. Optionally, all features can be regenerated.
080         *
081         * @param cacheDir
082         *            the location of the cache
083         * @param extractor
084         *            the feature extractor
085         * @param force
086         *            if true, then all features will be regenerated and saved,
087         *            rather than being loaded.
088         */
089        public DiskCachingFeatureExtractor(File cacheDir, FeatureExtractor<FEATURE, OBJECT> extractor, boolean force) {
090                this.cacheDir = cacheDir;
091                this.extractor = extractor;
092                this.force = force;
093
094                this.cacheDir.mkdirs();
095        }
096
097        @Override
098        public FEATURE extractFeature(OBJECT object) {
099                final File cachedFeature = new File(cacheDir, object.getID() + ".dat");
100                cachedFeature.getParentFile().mkdirs();
101
102                FEATURE feature = null;
103                if (!force && cachedFeature.exists()) {
104                        feature = load(cachedFeature);
105
106                        if (feature != null)
107                                return feature;
108                }
109
110                feature = extractor.extractFeature(object);
111
112                try {
113                        return write(feature, cachedFeature);
114                } catch (final IOException e) {
115                        logger.warn("Caching of the feature for the " + object.getID() + " object was disabled", e);
116                        return feature;
117                }
118        }
119
120        private FEATURE write(FEATURE feature, File cachedFeature) throws IOException {
121                if (feature instanceof WriteableBinary) {
122                        IOUtils.writeBinaryFull(cachedFeature, (WriteableBinary) feature);
123                } else {
124                        IOUtils.writeToFile(feature, cachedFeature);
125                }
126
127                return feature;
128        }
129
130        @SuppressWarnings("unchecked")
131        private FEATURE load(File cachedFeature) {
132                try {
133                        return (FEATURE) IOUtils.read(cachedFeature);
134                } catch (final Exception e) {
135                        try {
136                                return (FEATURE) IOUtils.readFromFile(cachedFeature);
137                        } catch (final IOException e1) {
138                                logger.warn("Error reading from cache. Feature will be regenerated.");
139                        }
140                }
141
142                return null;
143        }
144
145        @Override
146        public String toString() {
147                return this.extractor.toString();
148        }
149}