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.keypoints;
031
032import java.io.DataInput;
033import java.io.DataOutput;
034import java.io.IOException;
035import java.io.PrintWriter;
036import java.io.Serializable;
037import java.util.Arrays;
038import java.util.List;
039import java.util.Scanner;
040import java.util.StringTokenizer;
041
042import org.openimaj.feature.FloatFV;
043import org.openimaj.feature.local.LocalFeature;
044import org.openimaj.feature.local.list.MemoryLocalFeatureList;
045import org.openimaj.io.VariableLength;
046import org.openimaj.math.geometry.point.Point2d;
047import org.openimaj.math.geometry.point.ScaleSpacePoint;
048
049import Jama.Matrix;
050
051/**
052 * A local interest point with a location, scale, orientation and associated
053 * feature. The feature is stored as an array of floats.
054 *
055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
056 */
057public class FloatKeypoint
058implements
059Serializable,
060ScaleSpacePoint,
061LocalFeature<KeypointLocation, FloatFV>,
062VariableLength,
063Cloneable
064{
065        static final long serialVersionUID = 1234554345;
066
067        /**
068         * Default length of standard SIFT features.
069         */
070        private final static int DEFAULT_LENGTH = 128;
071
072        /**
073         * keypoint feature descriptor (i.e. SIFT)
074         */
075        public float[] vector;
076
077        /**
078         * dominant orientation of keypoint
079         */
080        public float ori;
081
082        /**
083         * scale of keypoint
084         */
085        public float scale;
086
087        /**
088         * x-position of keypoint
089         */
090        public float x;
091
092        /**
093         * y-position of keypoint
094         */
095        public float y;
096
097        /**
098         * Construct with the default feature vector length for SIFT (128).
099         */
100        public FloatKeypoint() {
101                this.vector = new float[DEFAULT_LENGTH];
102        }
103
104        /**
105         * Construct with the given feature vector length.
106         *
107         * @param length
108         *            the length of the feature vector
109         */
110        public FloatKeypoint(int length) {
111                if (length < 0)
112                        length = DEFAULT_LENGTH;
113                this.vector = new float[length];
114        }
115
116        /**
117         * Construct with the given parameters.
118         *
119         * @param x
120         *            the x-ordinate of the keypoint
121         * @param y
122         *            the y-ordinate of the keypoint
123         * @param ori
124         *            the orientation of the keypoint
125         * @param scale
126         *            the scale of the keypoint
127         * @param ivec
128         *            the feature vector of the keypoint
129         */
130        public FloatKeypoint(float x, float y, float ori, float scale, float[] ivec) {
131                this.x = x;
132                this.y = y;
133                this.ori = ori;
134                this.scale = scale;
135                this.vector = ivec;
136        }
137
138        /**
139         * Construct by copying from another {@link FloatKeypoint}
140         *
141         * @param k
142         *            the {@link FloatKeypoint} to copy from
143         */
144        public FloatKeypoint(FloatKeypoint k) {
145                this(k.x, k.y, k.ori, k.scale, Arrays.copyOf(k.vector, k.vector.length));
146        }
147
148        /**
149         * Construct from a {@link Keypoint}.
150         *
151         * @param k
152         */
153        public FloatKeypoint(Keypoint k) {
154                this.setLocation(k.getLocation());
155                this.vector = new float[k.ivec.length];
156
157                for (int i = 0; i < vector.length; i++) {
158                        vector[i] = k.ivec[i] + 128;
159                }
160        }
161
162        @Override
163        public Float getOrdinate(int dimension) {
164                if (dimension == 0)
165                        return x;
166                if (dimension == 1)
167                        return y;
168                if (dimension == 2)
169                        return scale;
170                return null;
171        }
172
173        @Override
174        public int getDimensions() {
175                return 3;
176        }
177
178        @Override
179        public float getX() {
180                return x;
181        }
182
183        @Override
184        public float getY() {
185                return y;
186        }
187
188        @Override
189        public void setX(float x) {
190                this.x = x;
191        }
192
193        @Override
194        public void setY(float y) {
195                this.y = y;
196        }
197
198        @Override
199        public float getScale() {
200                return scale;
201        }
202
203        @Override
204        public void setScale(float scale) {
205                this.scale = scale;
206        }
207
208        @Override
209        public String toString() {
210                return ("Keypoint(" + this.x + ", " + this.y + ", " + this.scale + ", " + this.ori + ")");
211        }
212
213        /**
214         * Test whether the location of this {@link FloatKeypoint} and another
215         * {@link FloatKeypoint} is the same.
216         *
217         * @param obj
218         *            the other keypoint
219         * @return true if the locations match; false otherwise.
220         */
221        public boolean locationEquals(Object obj) {
222                if (obj instanceof FloatKeypoint) {
223                        final FloatKeypoint kobj = (FloatKeypoint) obj;
224
225                        if (kobj.x == x && kobj.y == y && kobj.scale == scale)
226                                return true;
227                }
228
229                return super.equals(obj);
230        }
231
232        @Override
233        public boolean equals(Object obj) {
234                if (obj instanceof FloatKeypoint) {
235                        final FloatKeypoint kobj = (FloatKeypoint) obj;
236
237                        if (kobj.x == x && kobj.y == y && kobj.scale == scale && Arrays.equals(vector, kobj.vector))
238                                return true;
239                }
240
241                return super.equals(obj);
242        }
243
244        @Override
245        public int hashCode() {
246                int hash = 1;
247                hash = hash * 31 + Float.floatToIntBits(y);
248                hash = hash * 31 + Float.floatToIntBits(x);
249                hash = hash * 31 + Float.floatToIntBits(scale);
250                return hash;
251        }
252
253        @Override
254        public FloatKeypoint clone() {
255                final FloatKeypoint clone = new FloatKeypoint();
256
257                clone.x = x;
258                clone.ori = ori;
259                clone.y = y;
260                clone.scale = scale;
261
262                clone.vector = new float[vector.length];
263                System.arraycopy(vector, 0, clone.vector, 0, vector.length);
264
265                return clone;
266        }
267
268        @Override
269        public void copyFrom(Point2d p) {
270                setX(p.getX());
271                setY(p.getY());
272        }
273
274        @Override
275        public void writeBinary(DataOutput out) throws IOException {
276                getLocation().writeBinary(out);
277                out.writeInt(vector.length);
278                for (int i = 0; i < vector.length; i++)
279                        out.writeFloat(this.vector[i]);
280        }
281
282        @Override
283        public void writeASCII(PrintWriter out) throws IOException {
284                /* Output data for the keypoint. */
285                getLocation().writeASCII(out);
286                for (int i = 0; i < vector.length; i++) {
287                        if (i > 0 && i % 20 == 0)
288                                out.println();
289                        out.print(" " + vector[i]);
290                }
291                out.println();
292        }
293
294        @Override
295        public void readBinary(DataInput in) throws IOException {
296                final KeypointLocation l = getLocation();
297                l.readBinary(in);
298                setLocation(l);
299
300                vector = new float[in.readInt()];
301                for (int i = 0; i < vector.length; i++)
302                        vector[i] = in.readFloat();
303        }
304
305        @Override
306        public void readASCII(Scanner in) throws IOException {
307                final KeypointLocation l = getLocation();
308                l.readASCII(in);
309                setLocation(l);
310
311                int i = 0;
312                while (i < vector.length) {
313                        final String line = in.nextLine();
314                        final StringTokenizer st = new StringTokenizer(line);
315
316                        while (st.hasMoreTokens()) {
317                                vector[i] = Float.parseFloat(st.nextToken());
318                                i++;
319                        }
320                }
321        }
322
323        @Override
324        public byte[] binaryHeader() {
325                return "".getBytes();
326        }
327
328        @Override
329        public String asciiHeader() {
330                return "";
331        }
332
333        @Override
334        public FloatFV getFeatureVector() {
335                return new FloatFV(vector);
336        }
337
338        @Override
339        public KeypointLocation getLocation() {
340                return new KeypointLocation(x, y, ori, scale);
341        }
342
343        /**
344         * Set the location of this {@link FloatKeypoint}
345         *
346         * @param location
347         *            the location
348         */
349        public void setLocation(KeypointLocation location) {
350                x = location.x;
351                y = location.y;
352                scale = location.scale;
353                ori = location.orientation;
354        }
355
356        @Override
357        public void translate(float x, float y) {
358                this.x += x;
359                this.y += y;
360        }
361
362        @Override
363        public FloatKeypoint transform(Matrix transform) {
364                float xt = (float) transform.get(0, 0) * getX() + (float) transform.get(0, 1) * getY()
365                                + (float) transform.get(0, 2);
366                float yt = (float) transform.get(1, 0) * getX() + (float) transform.get(1, 1) * getY()
367                                + (float) transform.get(1, 2);
368                final float zt = (float) transform.get(2, 0) * getX() + (float) transform.get(2, 1) * getY()
369                                + (float) transform.get(2, 2);
370
371                xt /= zt;
372                yt /= zt;
373
374                return new FloatKeypoint(xt, yt, this.ori, this.scale, this.vector.clone());
375        }
376
377        @Override
378        public Point2d minus(Point2d a) {
379                final FloatKeypoint kp = this.clone();
380                kp.x = this.x - (int) a.getX();
381                kp.y = this.y - (int) a.getY();
382                return null;
383        }
384
385        @Override
386        public void translate(Point2d v) {
387                this.translate(v.getX(), v.getY());
388        }
389
390        @Override
391        public Point2d copy() {
392                return clone();
393        }
394
395        /**
396         * Convert a list of {@link Keypoint}s to {@link FloatKeypoint}s.
397         *
398         * @param keys
399         *            the {@link Keypoint}s to convert.
400         * @return the converted {@link FloatKeypoint}s.
401         */
402        public static MemoryLocalFeatureList<FloatKeypoint> convert(List<? extends Keypoint> keys) {
403                final MemoryLocalFeatureList<FloatKeypoint> out = new MemoryLocalFeatureList<FloatKeypoint>();
404
405                for (final Keypoint k : keys)
406                        out.add(new FloatKeypoint(k));
407
408                return out;
409        }
410
411        @Override
412        public void setOrdinate(int dimension, Number value) {
413                if (dimension == 0)
414                        x = value.floatValue();
415                if (dimension == 1)
416                        y = value.floatValue();
417        }
418}