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.image.feature.dense.gradient.dsift;
031
032import java.util.ArrayList;
033import java.util.List;
034
035import org.openimaj.feature.local.list.LocalFeatureList;
036import org.openimaj.feature.local.list.MemoryLocalFeatureList;
037import org.openimaj.image.FImage;
038import org.openimaj.image.Image;
039import org.openimaj.image.processing.convolution.FGaussianConvolve;
040import org.openimaj.image.processor.SinglebandImageProcessor;
041import org.openimaj.math.geometry.shape.Rectangle;
042import org.openimaj.util.array.ArrayUtils;
043import org.openimaj.util.pair.IntObjectPair;
044
045/**
046 * A scale-space pyramid of dense SIFT for {@link FImage}s. Dense sift features
047 * are extracted for the given bin sizes (scales). The image is optionally
048 * smoothed with a Gaussian before each scale.
049 * <p>
050 * The {@link PyramidDenseSIFT} is not thread safe, but is reusable like the
051 * {@link DenseSIFT} analyser.
052 * 
053 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
054 * @param <IMAGE>
055 *            Type of image to be processed.
056 * 
057 */
058public class PyramidDenseSIFT<IMAGE extends Image<?, IMAGE> & SinglebandImageProcessor.Processable<Float, FImage, IMAGE>>
059                extends
060                AbstractDenseSIFT<IMAGE>
061{
062        /**
063         * Scales at which the dense SIFT features are extracted. Each value is used
064         * as bin size for the {@link DenseSIFT}.
065         */
066        int[] sizes;
067
068        /**
069         * The image is smoothed by a Gaussian kernel of standard deviation size /
070         * magnificationFactor.
071         */
072        float magnificationFactor;
073
074        private List<AbstractDenseSIFT<IMAGE>> levels;
075
076        /**
077         * Construct the pyramid dense sift extractor. The magnification factor is
078         * used to determine how to smooth the image before extracting the features
079         * at each level: the smoothing sigma at each level is the bin size at that
080         * level divided by the magnification factor. If the magnification factor is
081         * 0, then no smoothing will be applied at any level.
082         * 
083         * @param dsift
084         *            the underlying dense sift extractor (typically a
085         *            {@link DenseSIFT} (or {@link ApproximateDenseSIFT}) or
086         *            {@link ColourDenseSIFT} depending on the image type).
087         * @param magFactor
088         *            the magnification factor
089         * @param sizes
090         *            the scales (bin sizes for dense sift)
091         */
092        public PyramidDenseSIFT(AbstractDenseSIFT<IMAGE> dsift, float magFactor, int... sizes) {
093                this.sizes = sizes;
094                this.magnificationFactor = magFactor;
095
096                levels = new ArrayList<AbstractDenseSIFT<IMAGE>>(sizes.length);
097                for (int i = 0; i < sizes.length; i++) {
098                        levels.add(dsift.clone());
099                }
100        }
101
102        @Override
103        public void analyseImage(IMAGE image, Rectangle originalBounds) {
104                final Rectangle bounds = originalBounds;
105
106                for (int i = 0; i < sizes.length; i++) {
107                        final int size = sizes[i];
108                        final int offset = (int) Math.floor(3f / 2f * (ArrayUtils.maxValue(sizes) - size));
109
110                        final IMAGE smoothed;
111                        if (magnificationFactor == 0) {
112                                smoothed = image;
113                        } else {
114                                final float sigma = size / magnificationFactor;
115                                smoothed = image.process(new FGaussianConvolve(sigma));
116                        }
117
118                        // extract DSIFT
119                        bounds.x = originalBounds.x + offset;
120                        bounds.y = originalBounds.y + offset;
121
122                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
123                        dsift.setBinWidth(size);
124                        dsift.setBinHeight(size);
125                        dsift.analyseImage(smoothed, bounds);
126                }
127        }
128
129        @Override
130        public LocalFeatureList<FloatDSIFTKeypoint> getFloatKeypoints() {
131                final LocalFeatureList<FloatDSIFTKeypoint> kpts = new MemoryLocalFeatureList<FloatDSIFTKeypoint>(getNumOriBins()
132                                * getNumBinsX() * getNumBinsY());
133
134                for (int i = 0; i < sizes.length; i++) {
135                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
136                        kpts.addAll(dsift.getFloatKeypoints());
137                }
138
139                return kpts;
140        }
141
142        @Override
143        public LocalFeatureList<ByteDSIFTKeypoint> getByteKeypoints() {
144                final LocalFeatureList<ByteDSIFTKeypoint> kpts = new MemoryLocalFeatureList<ByteDSIFTKeypoint>(getNumOriBins()
145                                * getNumBinsX() * getNumBinsY());
146
147                for (int i = 0; i < sizes.length; i++) {
148                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
149                        kpts.addAll(dsift.getByteKeypoints());
150                }
151
152                return kpts;
153        }
154
155        @Override
156        public LocalFeatureList<FloatDSIFTKeypoint> getFloatKeypoints(float energyThreshold) {
157                final LocalFeatureList<FloatDSIFTKeypoint> kpts = new MemoryLocalFeatureList<FloatDSIFTKeypoint>(getNumOriBins()
158                                * getNumBinsX() * getNumBinsY());
159
160                for (int i = 0; i < sizes.length; i++) {
161                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
162                        kpts.addAll(dsift.getFloatKeypoints(energyThreshold));
163                }
164
165                return kpts;
166        }
167
168        @Override
169        public LocalFeatureList<ByteDSIFTKeypoint> getByteKeypoints(float energyThreshold) {
170                final LocalFeatureList<ByteDSIFTKeypoint> kpts = new MemoryLocalFeatureList<ByteDSIFTKeypoint>(getNumOriBins()
171                                * getNumBinsX() * getNumBinsY());
172
173                for (int i = 0; i < sizes.length; i++) {
174                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
175                        kpts.addAll(dsift.getByteKeypoints(energyThreshold));
176                }
177
178                return kpts;
179        }
180
181        /**
182         * Get the SIFT descriptors from the previous call to
183         * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
184         * in the form of a list of local features with float vectors.
185         * 
186         * @return a list of {@link FloatDSIFTKeypoint}s.
187         */
188        public List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> getFloatKeypointsGrouped() {
189                final List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>>(
190                                sizes.length);
191
192                for (int i = 0; i < sizes.length; i++) {
193                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
194
195                        prs.add(new IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>(sizes[i], dsift.getFloatKeypoints()));
196                }
197
198                return prs;
199        }
200
201        /**
202         * Get the SIFT descriptors from the previous call to
203         * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
204         * in the form of a list of local features with byte vectors.
205         * 
206         * @return a list of {@link ByteDSIFTKeypoint}s.
207         */
208        public List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> getByteKeypointsGrouped() {
209                final List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>>(
210                                sizes.length);
211
212                for (int i = 0; i < sizes.length; i++) {
213                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
214
215                        prs.add(new IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>(sizes[i], dsift.getByteKeypoints()));
216                }
217
218                return prs;
219        }
220
221        /**
222         * Get the SIFT descriptors from the previous call to
223         * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
224         * in the form of a list of local features with float vectors. Only the
225         * features with an energy above the given threshold will be returned.
226         * 
227         * @param energyThreshold
228         *            the threshold on the feature energy
229         * 
230         * @return a list of {@link FloatDSIFTKeypoint}s.
231         */
232        public List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> getFloatKeypointsGrouped(float energyThreshold) {
233                final List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>>(
234                                sizes.length);
235
236                for (int i = 0; i < sizes.length; i++) {
237                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
238
239                        prs.add(new IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>(sizes[i], dsift
240                                        .getFloatKeypoints(energyThreshold)));
241                }
242
243                return prs;
244        }
245
246        /**
247         * Get the SIFT descriptors from the previous call to
248         * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
249         * in the form of a list of local features with byte vectors. Only the
250         * features with an energy above the given threshold will be returned.
251         * 
252         * @param energyThreshold
253         *            the threshold on the feature energy
254         * 
255         * @return a list of {@link ByteDSIFTKeypoint}s.
256         */
257        public List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> getByteKeypointsGrouped(float energyThreshold) {
258                final List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>>(
259                                sizes.length);
260
261                for (int i = 0; i < sizes.length; i++) {
262                        final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
263
264                        prs.add(new IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>(sizes[i],
265                                        dsift.getByteKeypoints(energyThreshold)));
266                }
267
268                return prs;
269        }
270
271        /**
272         * Get the computed raw dense SIFT descriptors from the previous call to
273         * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)} .
274         * The descriptors are grouped by the sizes at which they were extracted.
275         * 
276         * @return the descriptors.
277         */
278        public float[][][] getLevelDescriptors() {
279                final float[][][] descr = new float[sizes.length][][];
280
281                for (int i = 0; i < sizes.length; i++) {
282                        descr[i] = levels.get(i).getDescriptors();
283                }
284
285                return descr;
286        }
287
288        /**
289         * Get the bin sizes
290         * 
291         * @return the bin sizes
292         */
293        public int[] getSizes() {
294                return sizes;
295        }
296
297        /**
298         * Not supported.
299         * 
300         * @throws UnsupportedOperationException
301         */
302        @Override
303        public void setBinWidth(int size) {
304                throw new UnsupportedOperationException();
305        }
306
307        /**
308         * Not supported.
309         * 
310         * @throws UnsupportedOperationException
311         */
312        @Override
313        public void setBinHeight(int size) {
314                throw new UnsupportedOperationException();
315        }
316
317        /**
318         * This returns the bin size of the zeroth level only. {@inheritDoc}
319         */
320        @Override
321        public int getBinWidth() {
322                return sizes[0];
323        }
324
325        /**
326         * This returns the bin size of the zeroth level only. {@inheritDoc}
327         */
328        @Override
329        public int getBinHeight() {
330                return sizes[0];
331        }
332
333        @Override
334        public int getNumBinsX() {
335                return levels.get(0).getNumBinsX();
336        }
337
338        @Override
339        public int getNumBinsY() {
340                return levels.get(0).getNumBinsY();
341        }
342
343        @Override
344        public int getNumOriBins() {
345                return levels.get(0).getNumOriBins();
346        }
347
348        @Override
349        public float[][] getDescriptors() {
350                int len = 0;
351                for (int i = 0; i < sizes.length; i++) {
352                        len += levels.get(i).getDescriptors().length;
353                }
354
355                final float[][] descr = new float[len][];
356
357                int offset = 0;
358                for (int i = 0; i < sizes.length; i++) {
359                        final float[][] ldescr = levels.get(i).getDescriptors();
360                        for (int j = 0; j < ldescr.length; j++) {
361                                descr[offset++] = ldescr[j];
362                        }
363                }
364
365                return descr;
366        }
367}