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.feature.local.list;
031
032import java.io.BufferedInputStream;
033import java.io.DataInput;
034import java.io.DataOutput;
035import java.io.File;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.PrintWriter;
039import java.lang.reflect.Array;
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.Collections;
043
044import org.openimaj.data.RandomData;
045import org.openimaj.feature.local.LocalFeature;
046import org.openimaj.feature.local.LocationFilter;
047import org.openimaj.io.IOUtils;
048
049/**
050 * An in-memory list of local features.
051 * 
052 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
053 * 
054 * @param <T>
055 *            the type of local feature
056 */
057public class MemoryLocalFeatureList<T extends LocalFeature<?, ?>> extends ArrayList<T> implements LocalFeatureList<T> {
058        private static final long serialVersionUID = 1L;
059
060        protected int cached_veclen = -1;
061
062        /**
063         * Construct an empty feature list
064         */
065        public MemoryLocalFeatureList() {
066        }
067
068        /**
069         * Construct an empty list with the given feature-vector length.
070         * 
071         * @param veclen
072         *            the expected length of the feature vectors of each local
073         *            feature.
074         */
075        public MemoryLocalFeatureList(int veclen) {
076                super();
077                this.cached_veclen = veclen;
078        }
079
080        /**
081         * Construct a local feature list from the given collection of local
082         * features.
083         * 
084         * @param c
085         *            Collection of local feature to add to the list instance.
086         */
087        public MemoryLocalFeatureList(Collection<? extends T> c) {
088                super(c);
089                if (size() > 0)
090                        cached_veclen = this.get(0).getFeatureVector().length();
091        }
092
093        /**
094         * Construct an empty list with the given feature-vector length and
095         * pre-allocate the underlying array with space for initialCapacity local
096         * features. The list will automatically grow once initialCapacity is
097         * reached.
098         * 
099         * @param veclen
100         *            the expected length of the feature vectors of each local
101         *            feature.
102         * @param initialCapacity
103         *            the initial capacity of the list.
104         */
105        public MemoryLocalFeatureList(int veclen, int initialCapacity) {
106                super(initialCapacity);
107                this.cached_veclen = veclen;
108        }
109
110        /**
111         * Create a MemoryLocalFeatureList by reading all the local features from
112         * the specified file.
113         * 
114         * @param <T>
115         *            the type of local feature
116         * @param keypointFile
117         *            the file from which to read the features
118         * @param clz
119         *            the class of local feature
120         * @return a new MemoryLocalFeatureList populated with features from the
121         *         file
122         * @throws IOException
123         *             if an error occurs reading the file
124         */
125        public static <T extends LocalFeature<?, ?>> MemoryLocalFeatureList<T> read(File keypointFile, Class<T> clz)
126                        throws IOException
127        {
128                final boolean isBinary = IOUtils.isBinary(keypointFile, LocalFeatureList.BINARY_HEADER);
129                final MemoryLocalFeatureList<T> list = new MemoryLocalFeatureList<T>();
130
131                if (isBinary) {
132                        LocalFeatureListUtils.readBinary(keypointFile, list, clz);
133                } else {
134                        LocalFeatureListUtils.readASCII(keypointFile, list, clz);
135                }
136
137                return list;
138        }
139
140        /**
141         * Create a MemoryLocalFeatureList by reading all the local features from
142         * the specified stream.
143         * 
144         * @param <T>
145         *            the type of local feature
146         * @param stream
147         *            the input stream from which to read the features
148         * @param clz
149         *            the class of local feature
150         * @return a new MemoryLocalFeatureList populated with features from the
151         *         file
152         * @throws IOException
153         *             if an error occurs reading the file
154         */
155        public static <T extends LocalFeature<?, ?>> MemoryLocalFeatureList<T> read(InputStream stream, Class<T> clz)
156                        throws IOException
157        {
158                return read(new BufferedInputStream(stream), clz);
159        }
160
161        /**
162         * Create a MemoryLocalFeatureList by reading all the local features from
163         * the specified stream.
164         * 
165         * @param <T>
166         *            the type of local feature
167         * @param stream
168         *            the input stream from which to read the features
169         * @param clz
170         *            the class of local feature
171         * @return a new MemoryLocalFeatureList populated with features from the
172         *         file
173         * @throws IOException
174         *             if an error occurs reading the file
175         */
176        public static <T extends LocalFeature<?, ?>> MemoryLocalFeatureList<T> read(BufferedInputStream stream, Class<T> clz)
177                        throws IOException
178        {
179                final boolean isBinary = IOUtils.isBinary(stream, LocalFeatureList.BINARY_HEADER);
180                final MemoryLocalFeatureList<T> list = new MemoryLocalFeatureList<T>();
181
182                if (isBinary) {
183                        LocalFeatureListUtils.readBinary(stream, list, clz);
184                } else {
185                        LocalFeatureListUtils.readASCII(stream, list, clz);
186                }
187
188                return list;
189        }
190
191        /**
192         * Create a MemoryLocalFeatureList by reading all the local features from
193         * the specified {@link DataInput}. Reading of the header is skipped, and it
194         * is assumed that the data is in binary format.
195         * 
196         * @param <T>
197         *            the type of local feature
198         * @param in
199         *            the data input from which to read the features
200         * @param clz
201         *            the class of local feature
202         * @return a new MemoryLocalFeatureList populated with features from the
203         *         file
204         * @throws IOException
205         *             if an error occurs reading the file
206         */
207        public static <T extends LocalFeature<?, ?>> MemoryLocalFeatureList<T> readNoHeader(DataInput in, Class<T> clz)
208                        throws IOException
209        {
210                final MemoryLocalFeatureList<T> list = new MemoryLocalFeatureList<T>();
211
212                LocalFeatureListUtils.readBinary(in, list, clz);
213
214                return list;
215        }
216
217        @SuppressWarnings("unchecked")
218        @Override
219        public <Q> Q[] asDataArray(Q[] a) {
220                if (a.length < size()) {
221                        System.out.println(a.getClass());
222                        a = (Q[]) Array.newInstance(a.getClass().getComponentType(), size());
223                }
224
225                int i = 0;
226                for (final T t : this) {
227                        a[i++] = (Q) t.getFeatureVector().getVector();
228                }
229
230                return a;
231        }
232
233        @Override
234        public MemoryLocalFeatureList<T> randomSubList(int nelem) {
235                MemoryLocalFeatureList<T> kl;
236
237                if (nelem > size()) {
238                        kl = new MemoryLocalFeatureList<T>(this);
239                        Collections.shuffle(kl);
240                } else {
241                        final int[] rnds = RandomData.getUniqueRandomInts(nelem, 0, this.size());
242                        kl = new MemoryLocalFeatureList<T>(cached_veclen);
243
244                        for (final int idx : rnds)
245                                kl.add(this.get(idx));
246                }
247
248                return kl;
249        }
250
251        @Override
252        public void writeBinary(DataOutput out) throws IOException {
253                resetVecLength();
254                LocalFeatureListUtils.writeBinary(out, this);
255        }
256
257        @Override
258        public void writeASCII(PrintWriter out) throws IOException {
259                resetVecLength();
260                LocalFeatureListUtils.writeASCII(out, this);
261        }
262
263        @Override
264        public byte[] binaryHeader() {
265                return LocalFeatureList.BINARY_HEADER;
266        }
267
268        @Override
269        public String asciiHeader() {
270                return "";
271        }
272
273        /*
274         * @see org.openimaj.feature.local.list.LocalFeatureList#vecLength()
275         */
276        @Override
277        public int vecLength() {
278                resetVecLength();
279
280                if (cached_veclen == -1) {
281                        if (size() > 0) {
282                                cached_veclen = get(0).getFeatureVector().length();
283                        }
284                }
285                return cached_veclen;
286        }
287
288        /**
289         * Reset the internal feature vector length to the length of the first
290         * feature. You must call this if you change the length of the features
291         * within the list.
292         */
293        public void resetVecLength() {
294                if (size() > 0) {
295                        cached_veclen = get(0).getFeatureVector().length();
296                }
297        }
298
299        /**
300         * Create a new list by applying a {@link LocationFilter} to all the
301         * elements of this list. Only items which are accepted by the filter will
302         * be added to the new list.
303         * 
304         * @param locationFilter
305         *            the location filter
306         * @return a filtered list
307         */
308        public MemoryLocalFeatureList<T> filter(LocationFilter locationFilter) {
309                final MemoryLocalFeatureList<T> newlist = new MemoryLocalFeatureList<T>();
310                for (final T t : this) {
311                        if (locationFilter.accept(t.getLocation()))
312                                newlist.add(t);
313                }
314                return newlist;
315        }
316
317        @Override
318        public MemoryLocalFeatureList<T> subList(int fromIndex, int toIndex) {
319                return new MemoryLocalFeatureList<T>(super.subList(fromIndex, toIndex));
320        }
321}