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.connectedcomponent.proc;
031
032import java.util.List;
033
034import org.openimaj.feature.DoubleFV;
035import org.openimaj.feature.FeatureVectorProvider;
036import org.openimaj.image.pixel.ConnectedComponent;
037import org.openimaj.image.pixel.ConnectedComponent.ConnectMode;
038import org.openimaj.image.pixel.Pixel;
039import org.openimaj.image.processor.connectedcomponent.ConnectedComponentProcessor;
040import org.openimaj.math.util.Interpolation;
041
042/**
043 * Distance-from-centroid descriptor for convex shapes. Sweeps the 
044 * edge of the shape over all angles in 0..360 and records the distance
045 * from the centroid.
046 * 
047 * Scale invariance is optionally achieved by normalising the
048 * resultant vector to sum to 1.
049 * 
050 * Rotation invariance is optionally achieved by measuring angles
051 * from the dominant orientation of the connected component.
052 * 
053 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
054 *
055 */
056public class BoundaryDistanceDescriptor implements ConnectedComponentProcessor, FeatureVectorProvider<DoubleFV> {
057        /**
058         * The number of samples
059         */
060        public final static int DESCRIPTOR_LENGTH = 360;
061        
062        /**
063         * The descriptor vector, measusring distance from centroid per degree
064         */
065        public double [] descriptor = new double[DESCRIPTOR_LENGTH];
066        protected boolean normaliseScale;
067        protected boolean normaliseAngle;
068
069        /**
070         * Construct the BoundaryDistanceDescriptor with both scale and
071         * orientation normalisation enabled
072         */
073        public BoundaryDistanceDescriptor() {
074                this(true, true);
075        }
076        
077        /**
078         * Construct the BoundaryDistanceDescriptor with optional scale and
079         * orientation invariance.
080         * @param normaliseDistance enable scale invariance
081         * @param normaliseAngle enable rotation invariance
082         */
083        public BoundaryDistanceDescriptor(boolean normaliseDistance, boolean normaliseAngle) {
084                this.normaliseScale = normaliseDistance;
085                this.normaliseAngle = normaliseAngle;
086        }
087
088        @Override
089        public void process(ConnectedComponent cc) {
090                cc = new ConnectedComponent(cc.calculateConvexHull()); //make shape convex
091                
092                List<Pixel> bound = cc.getInnerBoundary(ConnectMode.CONNECT_8);
093                double [] centroid = cc.calculateCentroid();
094                double direction = cc.calculateDirection();
095
096                float[] distances = new float[bound.size()];
097                float[] angle = new float[bound.size()];
098                int count = 0;
099
100                for (int i=0; i<bound.size(); i++) {
101                        Pixel p = bound.get(i);
102                        double o = p.y - centroid[1];
103                        double a = p.x - centroid[0];
104
105                        float dist = (float) Math.sqrt((a*a) + (o*o));
106                        distances[i] = dist;
107
108                        if (normaliseAngle) {
109                                angle[i] = (float) (direction - Math.atan2(o, a));
110                        } else {
111                                angle[i] = (float) (Math.atan2(o, a));
112                        }
113                                        
114                        angle[i] = (float) ((angle[i] %= 2.0*Math.PI) >= 0 ? angle[i] : (angle[i] + 2.0*Math.PI));                      
115                        angle[i] = (float) (360.0 * angle[i] / (2.0*Math.PI));
116                }
117
118                for (int i=0; i<DESCRIPTOR_LENGTH; i++) {
119                        int index1 = -1;
120                        int index2 = -1;
121
122                        for (int j=0; j<angle.length; j++) {
123                                int n = ((j+1 == angle.length) ? 0 : j+1);
124
125                                float aj = angle[j];
126                                float an = angle[n];
127
128                                if (an > 350 && aj < 10) if (i<10) an-=360; else aj+=360;
129                                if (aj > 350 && an < 10) if (i<10) aj-=360; else an+=360;
130                                
131                                if (aj==i) {
132                                        index1 = j;
133                                        index2 = j;
134                                        break;
135                                } else if (aj<an) {
136                                        if (i <= an && i > aj) {
137                                                index1 = j;
138                                                index2 = n;
139                                                break;
140                                        }
141                                } else {
142                                        if (i <= aj && i > an) {
143                                                index1 = j;
144                                                index2 = n;
145                                                break;
146                                        }
147                                }
148                        }
149
150                        
151                        descriptor[i] = Interpolation.lerp(i, angle[index1], distances[index1], angle[index2], distances[index2]);
152                        count += descriptor[i];
153                }
154                
155                if (normaliseScale) {
156                        for (int i=0; i<DESCRIPTOR_LENGTH; i++)
157                                descriptor[i] /= count;
158                }
159        }
160
161        /**
162         * Get the feature vector as a double array
163         * @return the feature vector
164         */
165        public double[] getFeatureVectorArray() {
166                return descriptor;
167        }
168        
169        @Override
170        public DoubleFV getFeatureVector() {
171                return new DoubleFV(getFeatureVectorArray());
172        }
173}