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.feature.global;
031
032import gnu.trove.map.hash.TObjectFloatHashMap;
033import gnu.trove.procedure.TObjectFloatProcedure;
034
035import org.openimaj.citation.annotation.Reference;
036import org.openimaj.citation.annotation.ReferenceType;
037import org.openimaj.feature.DoubleFV;
038import org.openimaj.feature.FeatureVectorProvider;
039import org.openimaj.image.MBFImage;
040import org.openimaj.image.analyser.ImageAnalyser;
041import org.openimaj.image.pixel.ConnectedComponent;
042import org.openimaj.image.pixel.PixelSet;
043import org.openimaj.image.saliency.AchantaSaliency;
044import org.openimaj.image.saliency.YehSaliency;
045import org.openimaj.image.segmentation.FelzenszwalbHuttenlocherSegmenter;
046import org.openimaj.math.geometry.point.Point2dImpl;
047
048/**
049 * Implementation of the rule-of-thirds algorithm described by Yeh et al.
050 * <p>
051 * I've assumed that the distances to the power-points should be normalized with
052 * respect to the image size - this isn't explicit in the paper, but given that
053 * the sigma of the gaussian is fixed, it seems likely...
054 *
055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
056 */
057@Reference(
058                type = ReferenceType.Inproceedings,
059                author = { "Che-Hua Yeh", "Yuan-Chen Ho", "Brian A. Barsky", "Ming Ouhyoung" },
060                title = "Personalized Photograph Ranking and Selection System",
061                year = "2010",
062                booktitle = "Proceedings of ACM Multimedia",
063                pages = { "211", "220" },
064                month = "October",
065                customData = { "location", "Florence, Italy" })
066public class RuleOfThirds implements ImageAnalyser<MBFImage>, FeatureVectorProvider<DoubleFV> {
067        private static final double SIGMA = 0.17;
068        private static final Point2dImpl[] powerPoints = getPowerPoints();
069
070        YehSaliency saliencyGenerator;
071        private double asSum;
072        private double aseSum;
073
074        /**
075         * Construct a new {@link RuleOfThirds} with the default settings for the
076         * {@link YehSaliency} algorithm.
077         */
078        public RuleOfThirds() {
079                saliencyGenerator = new YehSaliency();
080        }
081
082        /**
083         * Construct a new {@link RuleOfThirds} with the given values for the
084         * {@link YehSaliency} algorithm.
085         *
086         * @param saliencySigma
087         *            smoothing for the {@link AchantaSaliency} class
088         * @param segmenterSigma
089         *            smoothing for {@link FelzenszwalbHuttenlocherSegmenter}.
090         * @param k
091         *            k value for {@link FelzenszwalbHuttenlocherSegmenter}.
092         * @param minSize
093         *            minimum region size for
094         *            {@link FelzenszwalbHuttenlocherSegmenter}.
095         */
096        public RuleOfThirds(float saliencySigma, float segmenterSigma, float k, int minSize) {
097                saliencyGenerator = new YehSaliency(saliencySigma, segmenterSigma, k, minSize);
098        }
099
100        @Override
101        public DoubleFV getFeatureVector() {
102                if (asSum == 0)
103                        new DoubleFV(new double[] { 0 });
104                return new DoubleFV(new double[] { aseSum / asSum });
105        }
106
107        /*
108         * (non-Javadoc)
109         *
110         * @see
111         * org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj
112         * .image.Image)
113         */
114        @Override
115        public void analyseImage(MBFImage image) {
116                final int width = image.getWidth();
117                final int height = image.getHeight();
118
119                image.analyseWith(saliencyGenerator);
120                final TObjectFloatHashMap<ConnectedComponent> componentMap = saliencyGenerator.getSaliencyComponents();
121
122                asSum = 0;
123                aseSum = 0;
124                componentMap.forEachEntry(new TObjectFloatProcedure<ConnectedComponent>() {
125                        @Override
126                        public boolean execute(ConnectedComponent c, float s) {
127                                final double as = c.calculateArea() * s;
128
129                                final double D = closestDistance(c, width, height);
130
131                                asSum += as;
132                                aseSum += as * Math.exp(-(D * D) / (2 * SIGMA));
133
134                                return true;
135                        }
136                });
137        }
138
139        private double closestDistance(PixelSet cc, int width, int height) {
140                final double centroid[] = cc.calculateCentroid();
141                double minDistance = Double.MAX_VALUE;
142
143                for (final Point2dImpl pt : powerPoints) {
144                        final double dx = (centroid[0] / width) - pt.x;
145                        final double dy = (centroid[1] / width) - pt.y;
146                        final double d = dx * dx + dy * dy;
147
148                        if (d < minDistance)
149                                minDistance = d;
150                }
151
152                return Math.sqrt(minDistance);
153        }
154
155        private static Point2dImpl[] getPowerPoints() {
156                return new Point2dImpl[] {
157                                new Point2dImpl(1 / 3f, 1 / 3f),
158                                new Point2dImpl(2 / 3f, 1 / 3f),
159                                new Point2dImpl(1 / 3f, 2 / 3f),
160                                new Point2dImpl(2 / 3f, 2 / 3f) };
161        }
162}