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.local.detector.ipd.finder;
031
032import java.util.TreeSet;
033
034import org.openimaj.feature.local.list.LocalFeatureList;
035import org.openimaj.image.feature.local.interest.IPDSelectionMode;
036import org.openimaj.image.feature.local.interest.InterestPointData;
037import org.openimaj.image.feature.local.interest.InterestPointDetector;
038import org.openimaj.image.feature.local.keypoints.InterestPointKeypoint;
039import org.openimaj.math.geometry.shape.Ellipse;
040
041/**
042 * A characteristic octave interest point finder throws {@link InterestPointData} away if two instances are similar. 
043 * Similarity is defined by the position, rotation and axis ratio of the two interest points.
044 * 
045 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
046 *
047 * @param <T>
048 */
049public class CharacteristicOctaveInterestPointFinder<T extends InterestPointData> extends OctaveInterestPointFinder<T> {
050
051        private static final double DEFAULT_MAX_DISTANCE = 4;
052        private static final double DEFAULT_MAX_ROTATION = (Math.PI / 180.0) * 15.0;
053        private static final double DEFAULT_MAX_AXIS_RATIO = 0.1;
054        /**
055         * The maximum distance before two keypoints are considered "similar"
056         */
057        public double maxDistance = DEFAULT_MAX_DISTANCE;
058        /**
059         * The maximum rotation difference before two keypoints are considered "similar"
060         */
061        public double maxRotation = DEFAULT_MAX_ROTATION;
062        /**
063         * The maximum axis ratio difference before two keypoints are considered similar
064         */
065        public double maxAxisRatio = DEFAULT_MAX_AXIS_RATIO;
066
067        /**
068         * construct this finder with the detector and selection mode
069         * @param detector
070         * @param selectionMode
071         */
072        public CharacteristicOctaveInterestPointFinder(InterestPointDetector<T> detector, IPDSelectionMode selectionMode) {
073                super(detector, selectionMode);
074        }
075        
076        @Override
077        public void finish() {
078                LocalFeatureList<InterestPointKeypoint<T>> locatedFeatures = this.listener.getFeatures();
079                TreeSet<Integer> toRemove = new TreeSet<Integer>();
080                for (int i = 0; i < locatedFeatures.size(); i++) {
081                        InterestPointKeypoint<T> kp1 = locatedFeatures.get(i);
082                        for (int j = i+1; j < locatedFeatures.size(); j++) {
083                                InterestPointKeypoint<T> kp2 = locatedFeatures.get(j);
084                                if(similarTo(kp1,kp2)){
085                                        if(kp1.location.score >= kp2.location.score){
086                                                toRemove.add(j);
087                                        }
088                                        else{
089                                                toRemove.add(i);
090                                        }
091                                }
092                        }       
093                }
094                int nRemove = 0;
095                for(int index : toRemove){
096                        locatedFeatures.remove(index - nRemove++);
097                }
098        }
099
100        private boolean similarTo(InterestPointKeypoint<T> kp1,InterestPointKeypoint<T> kp2) {
101                boolean similar = true;
102                // Similar position
103                similar = Math.sqrt(Math.pow(kp1.x -kp2.x,2) +  Math.pow(kp1.y -kp2.y,2)) < maxDistance ; 
104                if(!similar) return false;
105                Ellipse e1 = kp1.location.getEllipse();
106                Ellipse e2 = kp2.location.getEllipse();
107                // Ellipse with a similar rotation
108                similar = Math.abs(e1.getRotation() - e2.getRotation()) < maxRotation; 
109                if(!similar) return false;
110                
111                // Similar semi-major and semi-minor axis ratio
112                similar = Math.abs((e1.getMinor()/e1.getMajor()) - (e2.getMinor()/e2.getMajor())) < maxAxisRatio ;
113                if(!similar) return false;
114                
115                return true;
116        }
117
118}