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.tools.clusterquantiser;
031
032import java.io.BufferedInputStream;
033import java.io.BufferedReader;
034import java.io.DataInputStream;
035import java.io.File;
036import java.io.FileInputStream;
037import java.io.IOException;
038import java.io.InputStream;
039import java.io.InputStreamReader;
040import java.util.Arrays;
041import java.util.Scanner;
042
043import org.openimaj.feature.local.list.LocalFeatureList;
044import org.openimaj.image.feature.local.affine.AffineSimulationKeypoint;
045import org.openimaj.image.feature.local.keypoints.Keypoint;
046import org.openimaj.io.IOUtils;
047
048/**
049 * Different file formats containing local features.
050 * 
051 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
052 * 
053 */
054public enum FileType {
055        /**
056         * Auto-guess between Lowe's ASCII keypoints format or the OpenIMAJ binary
057         * format.
058         * 
059         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
060         * 
061         */
062        LOWE_KEYPOINT {
063                @Override
064                public Header readHeader(File file) throws IOException {
065                        try {
066                                return BINARY_KEYPOINT.readHeader(file);
067                        } catch (final Exception e) {
068                                return LOWE_KEYPOINT_ASCII.readHeader(file);
069                        }
070                }
071
072                @Override
073                public Header readHeader(InputStream bis) throws IOException {
074                        final BufferedInputStream bstream = new BufferedInputStream(bis);
075
076                        final boolean binary = IOUtils.isBinary(bstream, LocalFeatureList.BINARY_HEADER);
077
078                        if (binary)
079                                return BINARY_KEYPOINT.readHeader(bstream);
080                        else
081                                return LOWE_KEYPOINT_ASCII.readHeader(bstream);
082                }
083
084                @Override
085                public FeatureFile read(File file) throws IOException {
086                        try {
087                                return BINARY_KEYPOINT.read(file);
088                        } catch (final Exception e) {
089                                return LOWE_KEYPOINT_ASCII.read(file);
090                        }
091                }
092
093                @Override
094                public FeatureFile read(InputStream stream) throws IOException {
095                        final BufferedInputStream bstream = new BufferedInputStream(stream);
096
097                        final boolean binary = IOUtils.isBinary(bstream, LocalFeatureList.BINARY_HEADER);
098
099                        if (binary)
100                                return BINARY_KEYPOINT.read(bstream);
101                        else
102                                return LOWE_KEYPOINT_ASCII.read(bstream);
103                }
104
105                @Override
106                public byte[][] readFeatures(File file, int... index) throws IOException {
107                        try {
108                                return BINARY_KEYPOINT.readFeatures(file, index);
109                        } catch (final Exception e) {
110                                return LOWE_KEYPOINT_ASCII.readFeatures(file, index);
111                        }
112                }
113
114                @Override
115                public byte[][] readFeatures(InputStream file, int... index) throws IOException {
116                        try {
117                                return BINARY_KEYPOINT.readFeatures(file, index);
118                        } catch (final Exception e) {
119                                return LOWE_KEYPOINT_ASCII.readFeatures(file, index);
120                        }
121                }
122        },
123        /**
124         * OpenIMAJ binary list of keypoints format
125         * 
126         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
127         * 
128         */
129        BINARY_KEYPOINT {
130                @Override
131                public Header readHeader(File file) throws IOException {
132                        BufferedInputStream bis = null;
133
134                        try {
135                                bis = new BufferedInputStream(new FileInputStream(file));
136                                final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
137                                bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
138
139                                if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
140                                        throw new IOException("File \"" + file + "\"is not a binary keypoint file");
141                                }
142
143                                final DataInputStream dis = new DataInputStream(bis);
144
145                                final Header h = new Header();
146                                h.nfeatures = dis.readInt();
147                                h.ndims = dis.readInt();
148                                return h;
149                        } finally {
150                                try {
151                                        bis.close();
152                                } catch (final IOException e) {
153                                }
154                        }
155                }
156
157                @Override
158                public Header readHeader(InputStream bis) throws IOException {
159                        final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
160                        bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
161
162                        if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
163                                throw new IOException("Stream does not contain a binary keypoint");
164                        }
165
166                        final DataInputStream dis = new DataInputStream(bis);
167
168                        final Header h = new Header();
169                        h.nfeatures = dis.readInt();
170                        h.ndims = dis.readInt();
171                        return h;
172                }
173
174                @Override
175                public FeatureFile read(File file) throws IOException {
176                        final FeatureFile ff = new StreamedFeatureFile(file);
177                        return ff;
178                }
179
180                @Override
181                public FeatureFile read(InputStream stream) throws IOException {
182                        final FeatureFile ff = new StreamedFeatureFile(stream);
183                        return ff;
184                }
185
186                @Override
187                public byte[][] readFeatures(File file, int... index) throws IOException {
188                        return readFeatures(new FileInputStream(file), index);
189                }
190
191                @Override
192                public byte[][] readFeatures(InputStream file, int... index) throws IOException {
193                        BufferedInputStream bis = null;
194                        final byte[][] data = new byte[index.length][];
195                        try {
196                                bis = new BufferedInputStream(file);
197                                final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
198                                bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
199
200                                if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
201                                        throw new IOException("File \"" + file + "\"is not a binary keypoint file");
202                                }
203
204                                final DataInputStream dis = new DataInputStream(bis);
205
206                                final Header h = new Header();
207                                h.nfeatures = dis.readInt();
208                                h.ndims = dis.readInt();
209
210                                // == float * 4 + int * KeypointEngine.VecLength
211                                final int vecLength = (16 + h.ndims);
212                                int skipped = 0;
213                                Arrays.sort(index);
214                                for (int i = 0; i < index.length; i++) {
215                                        int toSkip = (index[i] * vecLength) - skipped;
216                                        skipped += toSkip;
217                                        while (toSkip > 0)
218                                                toSkip -= dis.skip(toSkip);
219
220                                        final Keypoint kp = new Keypoint();
221                                        kp.x = dis.readFloat();
222                                        kp.y = dis.readFloat();
223                                        kp.scale = dis.readFloat();
224                                        kp.ori = dis.readFloat();
225                                        kp.ivec = new byte[h.ndims];
226                                        dis.read(kp.ivec, 0, h.ndims);
227                                        data[i] = kp.ivec;
228                                        skipped += vecLength;
229                                }
230
231                        } finally {
232                                try {
233                                        bis.close();
234                                } catch (final IOException e) {
235                                }
236                        }
237                        return data;
238                }
239        },
240        /**
241         * Format defined by Lowe's "keypoints" binary
242         * 
243         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
244         */
245        LOWE_KEYPOINT_ASCII {
246                @Override
247                public byte[][] readFeatures(File file, int... index) throws IOException {
248                        return AsciiInterestPoint.readData(file, index, false, AsciiInterestPoint.NUM_CIRCLE_LOC_FEATS);
249                }
250
251                @Override
252                public Header readHeader(File file) throws IOException {
253                        return AsciiInterestPoint.readHeader(file, false);
254                }
255
256                @Override
257                public Header readHeader(InputStream stream) throws IOException {
258                        return AsciiInterestPoint.readHeader(new Scanner(stream), false);
259                }
260
261                @Override
262                public byte[][] readFeatures(File file) throws IOException {
263                        return AsciiInterestPoint.readData(file, false, AsciiInterestPoint.NUM_CIRCLE_LOC_FEATS);
264                }
265
266                @Override
267                public FeatureFile read(File file) throws IOException {
268                        return AsciiInterestPoint.read(file, false, AsciiInterestPoint.NUM_CIRCLE_LOC_FEATS);
269                }
270
271                @Override
272                public FeatureFile read(InputStream source) throws IOException {
273                        return AsciiInterestPoint.read(source, false, AsciiInterestPoint.NUM_CIRCLE_LOC_FEATS);
274                }
275        },
276        /**
277         * Ellipse format used by Oxford tools
278         * 
279         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
280         */
281        ELLIPSE_ASCII {
282                @Override
283                public byte[][] readFeatures(File file, int... index) throws IOException {
284                        return AsciiInterestPoint.readData(file, index, true, AsciiInterestPoint.NUM_ELLIPSE_LOC_FEATS);
285                }
286
287                @Override
288                public Header readHeader(File file) throws IOException {
289                        return AsciiInterestPoint.readHeader(file, true);
290                }
291
292                @Override
293                public Header readHeader(InputStream stream) throws IOException {
294                        return AsciiInterestPoint.readHeader(new Scanner(stream), true);
295                }
296
297                @Override
298                public byte[][] readFeatures(File file) throws IOException {
299                        return AsciiInterestPoint.readData(file, true, AsciiInterestPoint.NUM_ELLIPSE_LOC_FEATS);
300                }
301
302                @Override
303                public FeatureFile read(File file) throws IOException {
304                        return AsciiInterestPoint.read(file, true, AsciiInterestPoint.NUM_ELLIPSE_LOC_FEATS);
305                }
306
307                @Override
308                public FeatureFile read(InputStream source) throws IOException {
309                        return AsciiInterestPoint.read(source, true, AsciiInterestPoint.NUM_ELLIPSE_LOC_FEATS);
310                }
311        },
312        /**
313         * KOEN1 ascii format used by Koen van der Sande's colour sift tools.
314         * 
315         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
316         * 
317         */
318        KOEN1_ASCII {
319                @Override
320                public FeatureFile read(InputStream file) throws IOException {
321                        // create a BufferedReader for the file
322                        final BufferedReader input = new BufferedReader(new InputStreamReader(file));
323
324                        // read the first line and check that it starts with KOEN1
325                        // this way there is no need to worry about newline characters
326                        // in the end of string, if we used .equals
327                        if (!(input.readLine().startsWith("KOEN1"))) {
328                                throw new IOException(
329                                                "The specified file is not a KOEN1 type file");
330                        } else {
331                                // read the next two lines and Integer.parseInt(); to get ndims
332                                // & nfeatures
333                                final int ndims = Integer.parseInt(input.readLine());
334                                final int nfeatures = Integer.parseInt(input.readLine());
335
336                                final byte[][] data = new byte[nfeatures][ndims];
337                                final String[] locations = new String[nfeatures];
338
339                                if (nfeatures == 0) {
340                                        final FeatureFile ff = new MemoryFeatureFile(new byte[0][], new String[0]);
341                                        return ff;
342                                }
343
344                                for (int i = 0; i < nfeatures; i++) {
345
346                                        // read the next line and split on ';'
347                                        final String[] parts = input.readLine().split(";");
348
349                                        // put first element (substring) of the split into
350                                        // FeatureFile.locationInfo
351                                        locations[i] = parts[0];
352
353                                        // split second element (substring) on ' ' (a space)
354                                        final String[] fvector = parts[1].trim().split(" ");
355                                        // parse each element as int and put into array
356                                        for (int j = 0; j < ndims; j++) {
357                                                // store array in FeatureFiel.data
358                                                data[i][j] = (byte) (Integer.parseInt(fvector[j]) - 128);
359                                        }
360
361                                }
362                                final FeatureFile ff = new MemoryFeatureFile(data, locations);
363                                // return FeatureFile
364                                return ff;
365                        }
366                }
367
368                @Override
369                public FeatureFile read(File source) throws IOException {
370                        return read(new FileInputStream(source));
371                }
372        },
373        /**
374         * OpenIMAJ ASIFTENRICHED format
375         * 
376         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
377         * 
378         */
379        ASIFTENRICHED {
380                @Override
381                public Header readHeader(File file) throws IOException {
382
383                        try {
384                                return ASIFTENRICHED_BINARY.readHeader(file);
385                        }
386                        catch (final Exception e) {
387                                return ASIFTENRICHED_ASCII.readHeader(file);
388                        }
389                }
390
391                @Override
392                public Header readHeader(InputStream bis) throws IOException {
393                        final BufferedInputStream bstream = new BufferedInputStream(bis);
394
395                        final boolean binary = IOUtils.isBinary(bstream, LocalFeatureList.BINARY_HEADER);
396
397                        if (binary)
398                                return ASIFTENRICHED_BINARY.readHeader(bstream);
399                        else
400                                return ASIFTENRICHED_ASCII.readHeader(bstream);
401
402                }
403
404                @Override
405                public FeatureFile read(File file) throws IOException {
406                        try {
407                                return ASIFTENRICHED_BINARY.read(file);
408                        }
409                        catch (final Exception e) {
410                                return ASIFTENRICHED_ASCII.read(file);
411                        }
412                }
413
414                @Override
415                public FeatureFile read(InputStream stream) throws IOException {
416                        final BufferedInputStream bstream = new BufferedInputStream(stream);
417
418                        final boolean binary = IOUtils.isBinary(bstream, LocalFeatureList.BINARY_HEADER);
419
420                        if (binary)
421                                return ASIFTENRICHED_BINARY.read(bstream);
422                        else
423                                return ASIFTENRICHED_ASCII.read(bstream);
424                }
425
426                @Override
427                public byte[][] readFeatures(File file, int... index) throws IOException {
428                        try {
429                                return ASIFTENRICHED_BINARY.readFeatures(file, index);
430                        }
431                        catch (final Exception e) {
432                                return ASIFTENRICHED_ASCII.readFeatures(file, index);
433                        }
434                }
435
436                @Override
437                public byte[][] readFeatures(InputStream file, int... index) throws IOException {
438                        try {
439                                return ASIFTENRICHED_BINARY.readFeatures(file, index);
440                        }
441                        catch (final Exception e) {
442                                return ASIFTENRICHED_ASCII.readFeatures(file, index);
443                        }
444                }
445        },
446        /**
447         * OpenIMAJ ASIFTENRICHED binary format
448         * 
449         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
450         */
451        ASIFTENRICHED_BINARY {
452                @Override
453                public Header readHeader(File file) throws IOException {
454                        BufferedInputStream bis = null;
455
456                        try {
457                                bis = new BufferedInputStream(new FileInputStream(file));
458                                final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
459                                bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
460
461                                if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
462                                        throw new IOException("File \"" + file + "\"is not a binary keypoint file");
463                                }
464
465                                final DataInputStream dis = new DataInputStream(bis);
466
467                                final Header h = new Header();
468                                h.nfeatures = dis.readInt();
469                                h.ndims = dis.readInt();
470                                return h;
471                        } finally {
472                                try {
473                                        bis.close();
474                                } catch (final IOException e) {
475                                }
476                        }
477                }
478
479                @Override
480                public Header readHeader(InputStream bis) throws IOException {
481                        final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
482                        bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
483
484                        if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
485                                throw new IOException("Strean dies not contain a binary keypoint");
486                        }
487
488                        final DataInputStream dis = new DataInputStream(bis);
489
490                        final Header h = new Header();
491                        h.nfeatures = dis.readInt();
492                        h.ndims = dis.readInt();
493                        return h;
494                }
495
496                @Override
497                public FeatureFile read(File file) throws IOException {
498                        final StreamedFeatureFile ff = new StreamedFeatureFile(file, AffineSimulationKeypoint.class);
499                        ff.setIteratorType(AffineSimulationKeypointListArrayIterator.class);
500                        return ff;
501                }
502
503                @Override
504                public FeatureFile read(InputStream stream) throws IOException {
505                        final StreamedFeatureFile ff = new StreamedFeatureFile(stream, AffineSimulationKeypoint.class);
506                        ff.setIteratorType(AffineSimulationKeypointListArrayIterator.class);
507                        return ff;
508                }
509
510                @Override
511                public byte[][] readFeatures(File file, int... index) throws IOException {
512                        return readFeatures(new FileInputStream(file), index);
513                }
514
515                @Override
516                public byte[][] readFeatures(InputStream file, int... index) throws IOException {
517                        BufferedInputStream bis = null;
518                        final byte[][] data = new byte[index.length][];
519                        try {
520                                bis = new BufferedInputStream(file);
521                                final byte[] header = new byte[LocalFeatureList.BINARY_HEADER.length];
522                                bis.read(header, 0, LocalFeatureList.BINARY_HEADER.length);
523
524                                if (!Arrays.equals(header, LocalFeatureList.BINARY_HEADER)) {
525                                        throw new IOException("File \"" + file + "\"is not a binary keypoint file");
526                                }
527
528                                final DataInputStream dis = new DataInputStream(bis);
529
530                                final Header h = new Header();
531                                h.nfeatures = dis.readInt();
532                                h.ndims = dis.readInt();
533
534                                // == float * 6 + int + KeypointEngine.VecLength
535                                final int vecLength = (28 + h.ndims);
536                                int skipped = 0;
537                                Arrays.sort(index);
538                                for (int i = 0; i < index.length; i++) {
539                                        int toSkip = (index[i] * vecLength) - skipped;
540                                        skipped += toSkip;
541                                        while (toSkip > 0)
542                                                toSkip -= dis.skip(toSkip);
543
544                                        final AffineSimulationKeypoint kp = new AffineSimulationKeypoint(h.ndims);
545                                        kp.readBinary(dis);
546                                        data[i] = kp.ivec;
547                                        skipped += vecLength;
548                                }
549
550                        } finally {
551                                try {
552                                        bis.close();
553                                } catch (final IOException e) {
554                                }
555                        }
556                        return data;
557                }
558        },
559        /**
560         * OpenIMAJ ASIFTENRICHED ascii format
561         * 
562         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
563         */
564        ASIFTENRICHED_ASCII {
565                @Override
566                public byte[][] readFeatures(File file, int... index) throws IOException {
567                        return AsciiInterestPoint.readData(file, index, false, AsciiInterestPoint.NUM_ASIFT_LOC_FEATS);
568                }
569
570                @Override
571                public Header readHeader(File file) throws IOException {
572                        return AsciiInterestPoint.readHeader(file, false);
573                }
574
575                @Override
576                public Header readHeader(InputStream stream) throws IOException {
577                        return AsciiInterestPoint.readHeader(new Scanner(stream), false);
578                }
579
580                @Override
581                public byte[][] readFeatures(File file) throws IOException {
582                        return AsciiInterestPoint.readData(file, false, AsciiInterestPoint.NUM_ASIFT_LOC_FEATS);
583                }
584
585                @Override
586                public FeatureFile read(File file) throws IOException {
587                        return AsciiInterestPoint.read(file, false, AsciiInterestPoint.NUM_ASIFT_LOC_FEATS);
588                }
589
590                @Override
591                public FeatureFile read(InputStream source) throws IOException {
592                        return AsciiInterestPoint.read(source, false, AsciiInterestPoint.NUM_ASIFT_LOC_FEATS);
593                }
594        };
595
596        /**
597         * Read the header (num features and dimensionality of features) from given
598         * file. Override for performance.
599         * 
600         * @param file
601         * @return header
602         * @throws IOException
603         */
604        public Header readHeader(File file) throws IOException {
605                final Header header = new Header();
606
607                final FeatureFile ff = read(file);
608                if (ff.size() > 0) {
609                        header.nfeatures = ff.size();
610                        header.ndims = ff.get(0).data.length;
611                } else {
612                        header.nfeatures = 0;
613                        header.ndims = 0;
614                }
615
616                return header;
617        }
618
619        /**
620         * Read the header (num features and dimensionality of features) from given
621         * file. Override for performance.
622         * 
623         * @param stream
624         * @return header
625         * @throws IOException
626         */
627        public Header readHeader(InputStream stream) throws IOException {
628                final Header header = new Header();
629
630                final FeatureFile ff = read(stream);
631                if (ff.size() > 0) {
632                        header.nfeatures = ff.size();
633                        header.ndims = ff.get(0).data.length;
634                } else {
635                        header.nfeatures = 0;
636                        header.ndims = 0;
637                }
638
639                return header;
640        }
641
642        /**
643         * Read features at given indices from the file. Override for performance.
644         * 
645         * @param file
646         * @param index
647         * @return the feature data
648         * @throws IOException
649         */
650        public byte[][] readFeatures(File file, int... index) throws IOException {
651
652                return readFeatures(new FileInputStream(file), index);
653        }
654
655        /**
656         * Read features at given indices from an input stream. Override for
657         * performance.
658         * 
659         * @param stream
660         * @param index
661         * @return the feature data
662         * @throws IOException
663         */
664        public byte[][] readFeatures(InputStream stream, int... index) throws IOException {
665
666                final byte[][] features = readFeatures(stream);
667                final byte[][] selected = new byte[index.length][];
668                for (int i = 0; i < index.length; i++) {
669                        selected[i] = features[index[i]];
670                }
671                return selected;
672        }
673
674        /**
675         * Read all the features from the file. Override for performance.
676         * 
677         * @param file
678         * @return the feature data
679         * @throws IOException
680         */
681        public byte[][] readFeatures(File file) throws IOException {
682                final FeatureFile ff = read(file);
683                final byte[][] data = new byte[ff.size()][];
684                int i = 0;
685                for (final FeatureFileFeature fff : ff) {
686                        data[i++] = fff.data;
687                }
688                return data;
689        }
690
691        /**
692         * Read all the features from the file. Override for performance.
693         * 
694         * @param stream
695         * @return the feature data
696         * @throws IOException
697         */
698        public byte[][] readFeatures(InputStream stream) throws IOException {
699                final FeatureFile ff = read(stream);
700                final byte[][] data = new byte[ff.size()][];
701                int i = 0;
702                for (final FeatureFileFeature fff : ff) {
703                        data[i++] = fff.data;
704                }
705                return data;
706        }
707
708        /**
709         * Read a file
710         * 
711         * @param file
712         * @return the features
713         * @throws IOException
714         */
715        public abstract FeatureFile read(File file) throws IOException;
716
717        /**
718         * Read a file
719         * 
720         * @param source
721         * @return the features
722         * @throws IOException
723         */
724        public abstract FeatureFile read(InputStream source) throws IOException;
725}