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        /**
057         * Convert to string representation with the given format
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) _x += 0.5;
073                        if (_y >= 0.0) _y += 0.5;
074                        s += String.format(format, (int) x, (int) y, val);
075                }
076                
077                return s;
078        }
079        
080        /**
081         * Write feature as binary data
082         * @param os
083         * @throws IOException
084         */
085        public void writeFeatureBin(DataOutputStream os) throws IOException {
086                os.writeFloat(x);
087                os.writeFloat(y);
088                os.writeInt(val);
089        }
090
091        @Override
092        public float getX() {
093                return x;
094        }
095
096        @Override
097        public float getY() {
098                return y;
099        }
100
101        @Override
102        public void setX(float x) {
103                this.x = x;
104        }
105
106        @Override
107        public void setY(float y) {
108                this.y = y;
109        }
110        
111        @Override
112        public Feature clone() {
113                Feature f = new Feature();
114
115                f.x = x;
116                f.y = y;
117                f.val = val;
118//              f.aff_img = aff_img; 
119//              f.aff_img_gradx = aff_img_gradx;
120//              f.aff_img_grady = aff_img_grady;
121//              f.aff_x = aff_x;
122//              f.aff_y = aff_y;
123//              f.aff_Axx = aff_Axx;
124//              f.aff_Ayx = aff_Ayx;
125//              f.aff_Axy = aff_Axy;
126//              f.aff_Ayy = aff_Ayy;
127                
128                return f;
129        }
130        
131        @Override
132        public boolean equals(Object o) {
133                if (this == o) return true;
134                if (!(o instanceof Feature)) return false;
135                
136                if (((Feature)o).x == x && ((Feature)o).y == y && ((Feature)o).val == val) return true;
137                return false;
138        }
139        
140        @Override
141        public int hashCode() {
142                int hash = 17;
143                hash = (int) ((31 * hash) + x);
144                hash = (int) ((31 * hash) + y);
145                hash = ((31 * hash) + val);     
146//              hash = (int) ((31 * hash) + aff_img; 
147//              hash = (int) ((31 * hash) + aff_img_gradx;
148//              hash = (int) ((31 * hash) + aff_img_grady;
149//              hash = (int) ((31 * hash) + aff_x;
150//              hash = (int) ((31 * hash) + aff_y;
151//              hash = (int) ((31 * hash) + aff_Axx;
152//              hash = (int) ((31 * hash) + aff_Ayx;
153//              hash = (int) ((31 * hash) + aff_Axy;
154//              hash = (int) ((31 * hash) + aff_Ayy;
155                
156                return hash;
157        }
158        
159        @Override
160        public String toString() {
161                return "Feature(" + x + ", " + y + ", " + val + ")";
162        }
163
164        @Override
165    public void copyFrom( Point2d p )
166    {
167                setX( p.getX() );
168                setY( p.getY() );
169    }
170
171        @Override
172        public Float getOrdinate(int dimension) {
173                if (dimension == 0) return x;
174                return y;
175        }
176
177        @Override
178        public int getDimensions() {
179                return 2;
180        }
181
182        @Override
183        public void translate(float x, float y) {
184                this.x += x;
185                this.y += y;
186        }
187
188        @Override
189        public Feature transform(Matrix transform) {
190                if (transform.getRowDimension() == 3) {
191                        float xt = (float)transform.get(0, 0) * getX() + (float)transform.get(0, 1) * getY() + (float)transform.get(0, 2);
192                        float yt = (float)transform.get(1, 0) * getX() + (float)transform.get(1, 1) * getY() + (float)transform.get(1, 2);
193                        float zt = (float)transform.get(2, 0) * getX() + (float)transform.get(2, 1) * getY() + (float)transform.get(2, 2);
194                        
195                        xt /= zt;
196                        yt /= zt;
197                        
198                        Feature f = this.clone();
199                        f.x = xt;
200                        f.y = yt;
201                        return f;
202                } else if (transform.getRowDimension() == 2) {
203                        float xt = (float)transform.get(0, 0) * getX() + (float)transform.get(0, 1) * getY();
204                        float yt = (float)transform.get(1, 0) * getX() + (float)transform.get(1, 1) * getY();
205                        
206                        Feature f = this.clone();
207                        f.x = xt;
208                        f.y = yt;
209                        return f;
210                }
211                throw new IllegalArgumentException("Transform matrix has unexpected size");
212        }
213
214        @Override
215        public Point2d minus(Point2d a) {
216                Point2d p = this.clone();
217                p.setX(this.getX() - a.getX());
218                p.setY(this.getY() - a.getY());
219                return p;
220        }
221
222        @Override
223        public void readASCII(Scanner in) throws IOException {
224                x = in.nextFloat();
225                y = in.nextFloat();
226                val = in.nextInt();
227        }
228
229        @Override
230        public String asciiHeader() {
231                return this.getClass().getName();
232        }
233
234        @Override
235        public void readBinary(DataInput in) throws IOException {
236                x = in.readFloat();
237                y = in.readFloat();
238                val = in.readInt();
239        }
240
241        @Override
242        public byte[] binaryHeader() {
243                return this.getClass().getName().getBytes();
244        }
245
246        @Override
247        public void writeASCII(PrintWriter out) throws IOException {
248                out.format("%f %f %d", x, y, val);
249        }
250
251        @Override
252        public void writeBinary(DataOutput out) throws IOException {
253                out.writeFloat(x);
254                out.writeFloat(y);
255                out.writeInt(val);
256        }
257        
258        @Override
259        public void translate(Point2d v) {
260                this.translate(v.getX(), v.getY());
261        }
262
263        @Override
264        public Feature copy() {
265                return clone();
266        }
267}