001/**
002 * This source code file is part of a direct port of Stan Birchfield's implementation
003 * of a Kanade-Lucas-Tomasi feature tracker. The original implementation can be found
004 * here: http://www.ces.clemson.edu/~stb/klt/
005 *
006 * As per the original code, the source code is in the public domain, available
007 * for both commercial and non-commercial use.
008 */
009package org.openimaj.video.tracking.klt;
010
011import java.io.DataInput;
012import java.io.DataOutput;
013import java.io.DataOutputStream;
014import java.io.IOException;
015import java.io.PrintWriter;
016import java.util.Scanner;
017
018import org.openimaj.math.geometry.point.Point2d;
019
020import Jama.Matrix;
021
022/**
023 * A tracked feature
024 *
025 * @author Stan Birchfield
026 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
027 */
028public class Feature implements Point2d, Cloneable {
029        /**
030         * x ordinate of feature
031         */
032        public float x;
033
034        /**
035         * y ordinate of feature
036         */
037        public float y;
038
039        /**
040         * value of feature
041         */
042        public int val;
043
044        /* for affine mapping */
045        // public FImage aff_img;
046        // public FImage aff_img_gradx;
047        // public FImage aff_img_grady;
048        // public float aff_x;
049        // public float aff_y;
050        // public float aff_Axx;
051        // public float aff_Ayx;
052        // public float aff_Axy;
053        // public float aff_Ayy;
054
055        /**
056         * Convert to string representation with the given format
057         * 
058         * @param format
059         * @param type
060         * @return formatted string
061         */
062        public String toString(String format, String type) {
063                assert (type.equals("f") || type.equals("d"));
064                String s = "";
065
066                if (type.equals("f"))
067                        s += String.format(format, x, y, val);
068                else if (type.equals("d")) {
069                        /* Round x & y to nearest integer, unless negative */
070                        float _x = x;
071                        float _y = y;
072                        if (_x >= 0.0)
073                                _x += 0.5;
074                        if (_y >= 0.0)
075                                _y += 0.5;
076                        s += String.format(format, (int) x, (int) y, val);
077                }
078
079                return s;
080        }
081
082        /**
083         * Write feature as binary data
084         * 
085         * @param os
086         * @throws IOException
087         */
088        public void writeFeatureBin(DataOutputStream os) throws IOException {
089                os.writeFloat(x);
090                os.writeFloat(y);
091                os.writeInt(val);
092        }
093
094        @Override
095        public float getX() {
096                return x;
097        }
098
099        @Override
100        public float getY() {
101                return y;
102        }
103
104        @Override
105        public void setX(float x) {
106                this.x = x;
107        }
108
109        @Override
110        public void setY(float y) {
111                this.y = y;
112        }
113
114        @Override
115        public Feature clone() {
116                final Feature f = new Feature();
117
118                f.x = x;
119                f.y = y;
120                f.val = val;
121                // f.aff_img = aff_img;
122                // f.aff_img_gradx = aff_img_gradx;
123                // f.aff_img_grady = aff_img_grady;
124                // f.aff_x = aff_x;
125                // f.aff_y = aff_y;
126                // f.aff_Axx = aff_Axx;
127                // f.aff_Ayx = aff_Ayx;
128                // f.aff_Axy = aff_Axy;
129                // f.aff_Ayy = aff_Ayy;
130
131                return f;
132        }
133
134        @Override
135        public boolean equals(Object o) {
136                if (this == o)
137                        return true;
138                if (!(o instanceof Feature))
139                        return false;
140
141                if (((Feature) o).x == x && ((Feature) o).y == y && ((Feature) o).val == val)
142                        return true;
143                return false;
144        }
145
146        @Override
147        public int hashCode() {
148                int hash = 17;
149                hash = (int) ((31 * hash) + x);
150                hash = (int) ((31 * hash) + y);
151                hash = ((31 * hash) + val);
152                // hash = (int) ((31 * hash) + aff_img;
153                // hash = (int) ((31 * hash) + aff_img_gradx;
154                // hash = (int) ((31 * hash) + aff_img_grady;
155                // hash = (int) ((31 * hash) + aff_x;
156                // hash = (int) ((31 * hash) + aff_y;
157                // hash = (int) ((31 * hash) + aff_Axx;
158                // hash = (int) ((31 * hash) + aff_Ayx;
159                // hash = (int) ((31 * hash) + aff_Axy;
160                // hash = (int) ((31 * hash) + aff_Ayy;
161
162                return hash;
163        }
164
165        @Override
166        public String toString() {
167                return "Feature(" + x + ", " + y + ", " + val + ")";
168        }
169
170        @Override
171        public void copyFrom(Point2d p)
172        {
173                setX(p.getX());
174                setY(p.getY());
175        }
176
177        @Override
178        public Float getOrdinate(int dimension) {
179                if (dimension == 0)
180                        return x;
181                return y;
182        }
183
184        @Override
185        public int getDimensions() {
186                return 2;
187        }
188
189        @Override
190        public void translate(float x, float y) {
191                this.x += x;
192                this.y += y;
193        }
194
195        @Override
196        public Feature transform(Matrix transform) {
197                if (transform.getRowDimension() == 3) {
198                        float xt = (float) transform.get(0, 0) * getX() + (float) transform.get(0, 1) * getY()
199                                        + (float) transform.get(0, 2);
200                        float yt = (float) transform.get(1, 0) * getX() + (float) transform.get(1, 1) * getY()
201                                        + (float) transform.get(1, 2);
202                        final float zt = (float) transform.get(2, 0) * getX() + (float) transform.get(2, 1) * getY()
203                                        + (float) transform.get(2, 2);
204
205                        xt /= zt;
206                        yt /= zt;
207
208                        final Feature f = this.clone();
209                        f.x = xt;
210                        f.y = yt;
211                        return f;
212                } else if (transform.getRowDimension() == 2) {
213                        final float xt = (float) transform.get(0, 0) * getX() + (float) transform.get(0, 1) * getY();
214                        final float yt = (float) transform.get(1, 0) * getX() + (float) transform.get(1, 1) * getY();
215
216                        final Feature f = this.clone();
217                        f.x = xt;
218                        f.y = yt;
219                        return f;
220                }
221                throw new IllegalArgumentException("Transform matrix has unexpected size");
222        }
223
224        @Override
225        public Point2d minus(Point2d a) {
226                final Point2d p = this.clone();
227                p.setX(this.getX() - a.getX());
228                p.setY(this.getY() - a.getY());
229                return p;
230        }
231
232        @Override
233        public void readASCII(Scanner in) throws IOException {
234                x = in.nextFloat();
235                y = in.nextFloat();
236                val = in.nextInt();
237        }
238
239        @Override
240        public String asciiHeader() {
241                return this.getClass().getName();
242        }
243
244        @Override
245        public void readBinary(DataInput in) throws IOException {
246                x = in.readFloat();
247                y = in.readFloat();
248                val = in.readInt();
249        }
250
251        @Override
252        public byte[] binaryHeader() {
253                return this.getClass().getName().getBytes();
254        }
255
256        @Override
257        public void writeASCII(PrintWriter out) throws IOException {
258                out.format("%f %f %d", x, y, val);
259        }
260
261        @Override
262        public void writeBinary(DataOutput out) throws IOException {
263                out.writeFloat(x);
264                out.writeFloat(y);
265                out.writeInt(val);
266        }
267
268        @Override
269        public void translate(Point2d v) {
270                this.translate(v.getX(), v.getY());
271        }
272
273        @Override
274        public Feature copy() {
275                return clone();
276        }
277
278        @Override
279        public void setOrdinate(int dimension, Number value) {
280                if (dimension == 0)
281                        x = value.floatValue();
282                if (dimension == 1)
283                        y = value.floatValue();
284        }
285}