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.DataOutputStream;
012import java.io.File;
013import java.io.FileOutputStream;
014import java.io.IOException;
015import java.io.PrintWriter;
016import java.util.Iterator;
017
018import org.openimaj.image.FImage;
019import org.openimaj.image.MBFImage;
020import org.openimaj.math.geometry.shape.Rectangle;
021
022
023/**
024 * A list of features
025 * 
026 * @author Stan Birchfield
027 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
028 */
029public class FeatureList implements Iterable<Feature> {
030        /**
031         * The list of features
032         */
033        public Feature[] features;
034
035        /*********************************************************************
036         * KLTCreateFeatureList
037         * @param nFeatures 
038         */
039        public FeatureList(int nFeatures)
040        {
041                features = new Feature[nFeatures];
042
043                for (int i = 0 ; i < nFeatures ; i++) {
044                        features[i] = new Feature();
045                }
046        }
047
048        /*********************************************************************
049         * KLTCountRemainingFeatures
050         * @return the number of remaining features
051         */
052        public int countRemainingFeatures()
053        {
054                int count = 0;
055
056                for (Feature f : features)
057                        if (f.val >= 0) count++;
058
059                return count;
060        }
061        
062        @Override
063        public FeatureList clone(){
064                FeatureList ret = new FeatureList(this.features.length);
065                for(int i = 0; i < this.features.length; i++){
066                        ret.features[i] = this.features[i].clone();
067                }
068                return ret;
069                
070        }
071        
072        /*********************************************************************
073         * KLTWriteFeatureListToPPM
074         * @param img 
075         * @return a new image 
076         */
077        public MBFImage drawFeatures(FImage img)
078        {
079                /* Allocate memory for component images */
080                FImage redimg = img.clone();
081                FImage grnimg = img.clone();
082                FImage bluimg = img.clone();  
083
084                /* Overlay features in red */
085                for (int i = 0 ; i < features.length ; i++)
086                        if (features[i].val >= 0)  {
087                                int x = (int) (features[i].x + 0.5);
088                                int y = (int) (features[i].y + 0.5);
089                                for (int yy = y - 1 ; yy <= y + 1 ; yy++)
090                                        for (int xx = x - 1 ; xx <= x + 1 ; xx++)  
091                                                if (xx >= 0 && yy >= 0 && xx < img.width && yy < img.height)  {
092                                                        redimg.pixels[yy][xx] = 1f;
093                                                        grnimg.pixels[yy][xx] = 0f;
094                                                        bluimg.pixels[yy][xx] = 0f;
095                                                }
096                        }
097
098                return new MBFImage(redimg, grnimg, bluimg);
099        }
100        
101        /*********************************************************************
102         * KLTWriteFeatureListToPPM
103         * @param img 
104         * @return input image
105         */
106        public MBFImage drawFeatures(MBFImage img)
107        {
108                /* Allocate memory for component images */
109                MBFImage out = img;
110
111                /* Overlay features in red */
112                for (int i = 0 ; i < features.length ; i++)
113                        if (features[i].val >= 0)  {
114                                int x = (int) (features[i].x + 0.5);
115                                int y = (int) (features[i].y + 0.5);
116                                for (int yy = y - 1 ; yy <= y + 1 ; yy++)
117                                        for (int xx = x - 1 ; xx <= x + 1 ; xx++)  
118                                                if (xx >= 0 && yy >= 0 && xx < img.getWidth()&& yy < img.getHeight())  {
119                                                        out.bands.get(0).setPixel(xx, yy, 1.0f);
120                                                }
121                        }
122
123                return out;
124        }
125
126        /*********************************************************************
127         * KLTWriteFeatureList()
128         * 
129         * Writes features to file or to screen.
130         *
131         * INPUTS
132         * @param fname name of file to write data; if NULL, then print to stderr
133         * @param fmt   format for printing (e.g., "%5.1f" or "%3d");
134         *        if NULL, and if fname is not NULL, then write to binary file.
135         * @throws IOException 
136         */
137        public void writeFeatureList(File fname, String fmt) throws IOException
138        {
139                if (fmt != null) {  /* text file or stderr */ 
140                        if (fname != null) {
141                                PrintWriter bw = new PrintWriter(new FileOutputStream(fname)); 
142                                bw.write(toString(fmt, true));
143                                bw.close();
144                        } else {
145                                System.out.print(toString(fmt, false));
146                        }
147                } else {  /* binary file */
148                        DataOutputStream dos = new DataOutputStream(new FileOutputStream(fname));
149                        
150                        dos.write(IOUtils.binheader_fl.getBytes("US-ASCII"));
151                        dos.writeInt(features.length);
152                        for (Feature f : features)  {
153                                f.writeFeatureBin(dos);
154                        }
155                        
156                        dos.close();
157                }
158        }
159
160
161        /**
162         * Convert to a string representation with the given format.
163         * @param fmt
164         * @param comments
165         * @return the string representation.
166         */
167        public String toString(String fmt, boolean comments) {
168                String [] setup = IOUtils.setupTxtFormat(fmt);
169                String format = setup[0];
170                String type = setup[1];
171
172                String s = IOUtils.getHeader(format, IOUtils.StructureType.FEATURE_LIST, 0, features.length, comments);
173
174                for (int i = 0 ; i < features.length; i++)  {
175                        s += String.format("%7d | ", i);
176                        s += features[i].toString(format, type);
177                        s += String.format("\n");
178                }
179
180                return s;
181        }
182
183        @Override
184        public String toString() {
185                return toString("%3d", false);
186        }
187
188        @Override
189        public Iterator<Feature> iterator() {
190                Iterator<Feature> iterator = new Iterator<Feature>() {
191                        private int index = 0;
192                        
193                        @Override
194                        public boolean hasNext() {
195                                int newindex = index;
196                                while (newindex<features.length) {
197                                        if (features[newindex].val>=0) 
198                                                return true;
199                                        newindex++;
200                                }
201
202                                return false;
203                        }
204
205                        @Override
206                        public Feature next() {
207                                int newindex = index;
208                                while (newindex<features.length) {
209                                        if (features[newindex].val>=0) 
210                                                break;
211                                        newindex++;
212                                }
213                                
214                                Feature f = features[newindex];
215                                
216                                index++;
217                                
218                                return f;
219                        }
220
221                        @Override
222                        public void remove() {
223                                throw new UnsupportedOperationException("Remove not supported.");
224                        }                       
225                };
226                
227                return iterator;
228        }
229        
230        /**
231         *      Returns the bounding box of the features
232         *  @return the bounding box of the features
233         */
234        public Rectangle getBounds()
235        {
236                float minX = Float.MAX_VALUE;
237                float maxX = Float.MIN_VALUE;
238                float minY = Float.MAX_VALUE;
239                float maxY = Float.MIN_VALUE;
240                
241                for( Feature f : features )
242                {
243                        if( f.val >= 0 )
244                        {
245                                minX = Math.min( minX, f.x );
246                                maxX = Math.max( maxX, f.x );
247                                minY = Math.min( minY, f.y );
248                                maxY = Math.max( maxY, f.y );
249                        }
250                }
251                
252                return new Rectangle( minX, minY, maxX-minX, maxY-minY );
253        }
254}