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