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.localfeature.options;
031
032import java.io.ByteArrayInputStream;
033import java.io.IOException;
034import java.util.ArrayList;
035import java.util.List;
036
037import org.kohsuke.args4j.CmdLineOptionsProvider;
038import org.kohsuke.args4j.Option;
039import org.kohsuke.args4j.ProxyOptionHandler;
040import org.openimaj.feature.local.LocalFeature;
041import org.openimaj.feature.local.LocalFeatureExtractor;
042import org.openimaj.feature.local.list.LocalFeatureList;
043import org.openimaj.image.FImage;
044import org.openimaj.image.Image;
045import org.openimaj.image.ImageUtilities;
046import org.openimaj.image.MBFImage;
047import org.openimaj.image.colour.ColourSpace;
048import org.openimaj.image.colour.Transforms;
049import org.openimaj.image.feature.dense.gradient.dsift.ApproximateDenseSIFT;
050import org.openimaj.image.feature.dense.gradient.dsift.ByteDSIFTKeypoint;
051import org.openimaj.image.feature.dense.gradient.dsift.ColourDenseSIFT;
052import org.openimaj.image.feature.dense.gradient.dsift.DenseSIFT;
053import org.openimaj.image.feature.dense.gradient.dsift.FloatDSIFTKeypoint;
054import org.openimaj.image.feature.dense.gradient.dsift.PyramidDenseSIFT;
055import org.openimaj.image.feature.local.affine.AffineSimulationKeypoint;
056import org.openimaj.image.feature.local.affine.BasicASIFT;
057import org.openimaj.image.feature.local.affine.ColourASIFT;
058import org.openimaj.image.feature.local.engine.DoGColourSIFTEngine;
059import org.openimaj.image.feature.local.engine.DoGSIFTEngine;
060import org.openimaj.image.feature.local.engine.MinMaxDoGSIFTEngine;
061import org.openimaj.image.feature.local.engine.asift.ASIFTEngine;
062import org.openimaj.image.feature.local.engine.asift.ColourASIFTEngine;
063import org.openimaj.image.feature.local.keypoints.Keypoint;
064import org.openimaj.image.feature.local.keypoints.MinMaxKeypoint;
065import org.openimaj.tools.localfeature.options.ColourMode.ColourModeOp;
066import org.openimaj.tools.localfeature.options.ImageTransform.ImageTransformOp;
067
068/**
069 * Types of local feature
070 * 
071 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
072 */
073public enum LocalFeatureMode implements CmdLineOptionsProvider {
074        /**
075         * Difference-of-Gaussian SIFT
076         */
077        SIFT {
078                @Override
079                public AbstractDoGSIFTModeOp getOptions() {
080                        return new SiftMode(SIFT);
081                }
082        },
083        /**
084         * Min/Max Difference-of-Gaussian SIFT
085         */
086        MIN_MAX_SIFT {
087                @Override
088                public LocalFeatureModeOp getOptions() {
089                        return new MinMaxSiftMode(MIN_MAX_SIFT);
090                }
091        },
092        /**
093         * Affine simulated Difference-of-Gaussian SIFT (ASIFT). Outputs x, y,
094         * scale, ori + feature
095         */
096        ASIFT {
097                @Override
098                public LocalFeatureModeOp getOptions() {
099                        return new AsiftMode(ASIFT);
100                }
101        },
102        /**
103         * Enhanced output affine simulated Difference-of-Gaussian SIFT (ASIFT).
104         * Outputs x, y, scale, ori , tilt, theta, simulation index
105         */
106        ASIFTENRICHED {
107                @Override
108                public LocalFeatureModeOp getOptions() {
109                        return new AsiftEnrichedMode(ASIFTENRICHED);
110                }
111        },
112        /**
113         * Dense SIFT
114         */
115        DENSE_SIFT {
116                @Override
117                public LocalFeatureModeOp getOptions() {
118                        return new DenseSiftMode(DENSE_SIFT);
119                }
120        },
121        /**
122         * Colour Dense SIFT
123         */
124        COLOUR_DENSE_SIFT {
125                @Override
126                public LocalFeatureModeOp getOptions() {
127                        return new ColourDenseSiftMode(COLOUR_DENSE_SIFT);
128                }
129        },
130        /**
131         * Dense SIFT in a pyramid
132         */
133        PYRAMID_DENSE_SIFT {
134                @Override
135                public LocalFeatureModeOp getOptions() {
136                        return new PyramidDenseSiftMode(DENSE_SIFT);
137                }
138        },
139        /**
140         * Dense colour SIFT in a pyramid
141         */
142        PYRAMID_COLOUR_DENSE_SIFT {
143                @Override
144                public LocalFeatureModeOp getOptions() {
145                        return new PyramidColourDenseSiftMode(COLOUR_DENSE_SIFT);
146                }
147        };
148
149        @Override
150        public abstract LocalFeatureModeOp getOptions();
151
152        /**
153         * Associated options for each {@link LocalFeatureMode}.
154         * 
155         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
156         */
157        public static abstract class LocalFeatureModeOp
158                        implements
159                        LocalFeatureExtractor<LocalFeature<?, ?>, MBFImage>
160        {
161                private LocalFeatureMode mode;
162
163                @Option(
164                                name = "--image-transform",
165                                aliases = "-it",
166                                required = false,
167                                usage = "Optionally perform a image transform before keypoint calculation",
168                                handler = ProxyOptionHandler.class)
169                protected ImageTransform it = ImageTransform.NOTHING;
170                protected ImageTransformOp itOp = ImageTransform.NOTHING.getOptions();
171
172                /**
173                 * Extract features based on the options.
174                 * 
175                 * @param image
176                 *            the image
177                 * @return the features
178                 * @throws IOException
179                 */
180                public abstract LocalFeatureList<? extends LocalFeature<?, ?>> extract(byte[] image) throws IOException;
181
182                private LocalFeatureModeOp(LocalFeatureMode mode) {
183                        this.mode = mode;
184                }
185
186                /**
187                 * @return the name of the mode
188                 */
189                public String name() {
190                        return mode.name();
191                }
192
193                /**
194                 * @return the mode
195                 */
196                public LocalFeatureMode getMode() {
197                        return mode;
198                }
199        }
200
201        /**
202         * Associated options for things built on a {@link DoGSIFTEngine}.
203         * 
204         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
205         */
206        public static abstract class AbstractDoGSIFTModeOp extends LocalFeatureModeOp {
207                @Option(
208                                name = "--colour-mode",
209                                aliases = "-cm",
210                                required = false,
211                                usage = "Optionally perform sift using the colour of the image in some mode",
212                                handler = ProxyOptionHandler.class)
213                protected ColourMode cm = ColourMode.INTENSITY;
214                protected ColourModeOp cmOp = (ColourModeOp) ColourMode.INTENSITY.getOptions();
215
216                @Option(
217                                name = "--no-double-size",
218                                aliases = "-nds",
219                                required = false,
220                                usage = "Double the image sizes for the first iteration")
221                protected boolean noDoubleImageSize = false;
222
223                protected AbstractDoGSIFTModeOp(LocalFeatureMode mode) {
224                        super(mode);
225                }
226        }
227
228        private static class SiftMode extends AbstractDoGSIFTModeOp {
229                private SiftMode(LocalFeatureMode mode) {
230                        super(mode);
231                }
232
233                @Override
234                public LocalFeatureList<Keypoint> extract(byte[] img) throws IOException {
235                        return extract(cmOp.process(img));
236                }
237
238                @Override
239                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
240                        return Keypoint.class;
241                }
242
243                @Override
244                public LocalFeatureList<Keypoint> extractFeature(MBFImage img) {
245                        return extract(cmOp.process(img));
246                }
247
248                private LocalFeatureList<Keypoint> extract(Image<?, ?> image) {
249                        LocalFeatureList<Keypoint> keys = null;
250                        switch (this.cm) {
251                        case SINGLE_COLOUR:
252                        case INTENSITY: {
253                                final DoGSIFTEngine engine = new DoGSIFTEngine();
254                                engine.getOptions().setDoubleInitialImage(!noDoubleImageSize);
255                                image = itOp.transform(image);
256
257                                keys = engine.findFeatures((FImage) image);
258                                break;
259                        }
260                        case INTENSITY_COLOUR: {
261                                final DoGColourSIFTEngine engine = new DoGColourSIFTEngine();
262                                engine.getOptions().setDoubleInitialImage(!noDoubleImageSize);
263                                image = itOp.transform(image);
264
265                                keys = engine.findFeatures((MBFImage) image);
266                                break;
267                        }
268                        }
269                        return keys;
270                }
271        }
272
273        private static class MinMaxSiftMode extends AbstractDoGSIFTModeOp {
274                private MinMaxSiftMode(LocalFeatureMode mode) {
275                        super(mode);
276                }
277
278                @Override
279                public LocalFeatureList<? extends Keypoint> extract(byte[] img) throws IOException {
280                        final MinMaxDoGSIFTEngine engine = new MinMaxDoGSIFTEngine();
281                        LocalFeatureList<MinMaxKeypoint> keys = null;
282                        switch (this.cm) {
283                        case SINGLE_COLOUR:
284                        case INTENSITY:
285                                keys = engine.findFeatures((FImage) cmOp.process(img));
286                                break;
287                        case INTENSITY_COLOUR:
288                                throw new UnsupportedOperationException();
289                        }
290                        return keys;
291                }
292
293                @Override
294                public LocalFeatureList<? extends Keypoint> extractFeature(MBFImage img) {
295                        final MinMaxDoGSIFTEngine engine = new MinMaxDoGSIFTEngine();
296                        LocalFeatureList<MinMaxKeypoint> keys = null;
297                        switch (this.cm) {
298                        case SINGLE_COLOUR:
299                        case INTENSITY:
300                                keys = engine.findFeatures((FImage) cmOp.process(img));
301                                break;
302                        case INTENSITY_COLOUR:
303                                throw new UnsupportedOperationException();
304                        }
305                        return keys;
306                }
307
308                @Override
309                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
310                        return MinMaxKeypoint.class;
311                }
312        }
313
314        private static class AsiftMode extends AbstractDoGSIFTModeOp {
315                private AsiftMode(LocalFeatureMode mode) {
316                        super(mode);
317                }
318
319                @Option(
320                                name = "--n-tilts",
321                                required = false,
322                                usage = "The number of tilts for the affine simulation")
323                public int ntilts = 5;
324
325                @Override
326                public LocalFeatureList<Keypoint> extract(byte[] image) throws IOException {
327                        LocalFeatureList<Keypoint> keys = null;
328
329                        switch (this.cm) {
330                        case SINGLE_COLOUR:
331                        case INTENSITY:
332                                final BasicASIFT basic = new BasicASIFT(!noDoubleImageSize);
333                                basic.detectFeatures((FImage) itOp.transform(cmOp.process(image)), ntilts);
334                                keys = basic.getFeatures();
335                                break;
336                        case INTENSITY_COLOUR:
337                                final ColourASIFT colour = new ColourASIFT(!noDoubleImageSize);
338                                colour.detectFeatures((MBFImage) itOp.transform(cmOp.process(image)), ntilts);
339                        }
340                        return keys;
341                }
342
343                @Override
344                public LocalFeatureList<Keypoint> extractFeature(MBFImage image) {
345                        LocalFeatureList<Keypoint> keys = null;
346
347                        switch (this.cm) {
348                        case SINGLE_COLOUR:
349                        case INTENSITY:
350                                final BasicASIFT basic = new BasicASIFT(!noDoubleImageSize);
351                                basic.detectFeatures((FImage) itOp.transform(cmOp.process(image)), ntilts);
352                                keys = basic.getFeatures();
353                                break;
354                        case INTENSITY_COLOUR:
355                                final ColourASIFT colour = new ColourASIFT(!noDoubleImageSize);
356                                colour.detectFeatures((MBFImage) itOp.transform(cmOp.process(image)), ntilts);
357                        }
358                        return keys;
359                }
360
361                @Override
362                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
363                        return Keypoint.class;
364                }
365        }
366
367        private static class AsiftEnrichedMode extends AbstractDoGSIFTModeOp {
368                private AsiftEnrichedMode(LocalFeatureMode mode) {
369                        super(mode);
370                }
371
372                @Option(
373                                name = "--n-tilts",
374                                required = false,
375                                usage = "The number of tilts for the affine simulation")
376                public int ntilts = 5;
377
378                @Override
379                public LocalFeatureList<AffineSimulationKeypoint> extract(byte[] image) throws IOException {
380                        final ASIFTEngine engine = new ASIFTEngine(!noDoubleImageSize, ntilts);
381                        LocalFeatureList<AffineSimulationKeypoint> keys = null;
382                        switch (this.cm) {
383                        case SINGLE_COLOUR:
384                        case INTENSITY:
385                                FImage img = (FImage) cmOp.process(image);
386                                img = (FImage) itOp.transform(img);
387                                keys = engine.findFeatures(img);
388                                break;
389                        case INTENSITY_COLOUR:
390                                final ColourASIFTEngine colourengine = new ColourASIFTEngine(!noDoubleImageSize, ntilts);
391                                MBFImage colourimg = (MBFImage) cmOp.process(image);
392                                colourimg = (MBFImage) itOp.transform(colourimg);
393                                keys = colourengine.findFeatures(colourimg);
394                        }
395                        return keys;
396                }
397
398                @Override
399                public LocalFeatureList<AffineSimulationKeypoint> extractFeature(MBFImage image) {
400                        final ASIFTEngine engine = new ASIFTEngine(!noDoubleImageSize, ntilts);
401                        LocalFeatureList<AffineSimulationKeypoint> keys = null;
402                        switch (this.cm) {
403                        case SINGLE_COLOUR:
404                        case INTENSITY:
405                                FImage img = (FImage) cmOp.process(image);
406                                img = (FImage) itOp.transform(img);
407                                keys = engine.findFeatures(img);
408                                break;
409                        case INTENSITY_COLOUR:
410                                final ColourASIFTEngine colourengine = new ColourASIFTEngine(!noDoubleImageSize, ntilts);
411                                MBFImage colourimg = (MBFImage) cmOp.process(image);
412                                colourimg = (MBFImage) itOp.transform(colourimg);
413                                keys = colourengine.findFeatures(colourimg);
414                        }
415                        return keys;
416                }
417
418                @Override
419                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
420                        return AffineSimulationKeypoint.class;
421                }
422        }
423
424        private static abstract class AbstractDenseSiftMode extends LocalFeatureModeOp {
425                @Option(
426                                name = "--approximate",
427                                aliases = "-ap",
428                                required = false,
429                                usage = "Enable approximate mode (much faster)")
430                boolean approximate;
431
432                @Option(
433                                name = "--step-x",
434                                aliases = "-sx",
435                                required = false,
436                                usage = "Step size of sampling window in x-direction (in pixels)")
437                protected int stepX = 5;
438
439                @Option(
440                                name = "--step-y",
441                                aliases = "-sy",
442                                required = false,
443                                usage = "Step size of sampling window in y-direction (in pixels)")
444                protected int stepY = 5;
445
446                @Option(
447                                name = "--num-bins-x",
448                                aliases = "-nx",
449                                required = false,
450                                usage = "Number of spatial bins in the X direction")
451                protected int numBinsX = 4;
452
453                @Option(
454                                name = "--num-bins-y",
455                                aliases = "-ny",
456                                required = false,
457                                usage = "Number of spatial bins in the Y direction")
458                protected int numBinsY = 4;
459
460                @Option(name = "--num-ori-bins", aliases = "-no", required = false, usage = "The number of orientation bins")
461                protected int numOriBins = 8;
462
463                @Option(
464                                name = "--gaussian-window-size",
465                                aliases = "-gws",
466                                required = false,
467                                usage = "Size of the Gaussian window (in relative to of the size of a bin)")
468                protected float gaussianWindowSize = 2f;
469
470                @Option(name = "--clipping-threshold", required = false, usage = "Threshold for clipping the SIFT features")
471                protected float valueThreshold = 0.2f;
472
473                @Option(
474                                name = "--contrast-threshold",
475                                required = false,
476                                usage = "Threshold on the contrast of the returned features (-ve values disable this)")
477                protected float contrastThreshold = -1;
478
479                @Option(
480                                name = "--byte-features",
481                                required = false,
482                                usage = "Output features scaled to bytes rather than floats")
483                protected boolean byteFeatures = false;
484
485                private AbstractDenseSiftMode(LocalFeatureMode mode) {
486                        super(mode);
487                }
488        }
489
490        private static class DenseSiftMode extends AbstractDenseSiftMode {
491                @Option(
492                                name = "--bin-width",
493                                aliases = "-bw",
494                                required = false,
495                                usage = "Width of a single bin of the sampling window (in pixels). Sampling window width is this multiplied by #numBinX.")
496                protected int binWidth = 5;
497
498                @Option(
499                                name = "--bin-height",
500                                aliases = "-bh",
501                                required = false,
502                                usage = "Height of a single bin of the sampling window (in pixels). Sampling window height is this multiplied by #numBinY.")
503                protected int binHeight = 5;
504
505                private DenseSiftMode(LocalFeatureMode mode) {
506                        super(mode);
507                }
508
509                @Override
510                public LocalFeatureList<? extends LocalFeature<?, ?>> extract(byte[] image) throws IOException {
511                        return extract(ImageUtilities.readF(new ByteArrayInputStream(image)));
512                }
513
514                @Override
515                public LocalFeatureList<? extends LocalFeature<?, ?>> extractFeature(MBFImage image) {
516                        return extract(Transforms.calculateIntensityNTSC_LUT(image));
517                }
518
519                LocalFeatureList<? extends LocalFeature<?, ?>> extract(FImage image) {
520                        final DenseSIFT dsift;
521
522                        if (approximate)
523                                dsift = new ApproximateDenseSIFT(stepX, stepY, binWidth, binHeight, numBinsX, numBinsY, numOriBins,
524                                                gaussianWindowSize, valueThreshold);
525                        else
526                                dsift = new DenseSIFT(stepX, stepY, binWidth, binHeight, numBinsX, numBinsY, numOriBins,
527                                                gaussianWindowSize, valueThreshold);
528
529                        dsift.analyseImage(image);
530
531                        if (contrastThreshold <= 0) {
532                                if (byteFeatures)
533                                        return dsift.getByteKeypoints();
534                                return dsift.getFloatKeypoints();
535                        } else {
536                                if (byteFeatures)
537                                        return dsift.getByteKeypoints(contrastThreshold);
538                                return dsift.getFloatKeypoints(contrastThreshold);
539                        }
540                }
541
542                @Override
543                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
544                        if (byteFeatures)
545                                return ByteDSIFTKeypoint.class;
546                        return FloatDSIFTKeypoint.class;
547                }
548        }
549
550        private static class ColourDenseSiftMode extends DenseSiftMode {
551                @Option(name = "--colour-space", aliases = "-cs", required = false, usage = "Specify the colour space")
552                private ColourSpace colourspace = ColourSpace.RGB;
553
554                ColourDenseSiftMode(LocalFeatureMode mode) {
555                        super(mode);
556                }
557
558                @Override
559                public LocalFeatureList<? extends LocalFeature<?, ?>> extract(byte[] image) throws IOException {
560                        return extractFeature(ImageUtilities.readMBF(new ByteArrayInputStream(image)));
561                }
562
563                @Override
564                public LocalFeatureList<? extends LocalFeature<?, ?>> extractFeature(MBFImage image) {
565                        final ColourDenseSIFT dsift;
566
567                        if (approximate)
568                                dsift = new ColourDenseSIFT(new ApproximateDenseSIFT(stepX, stepY, binWidth, binHeight, numBinsX,
569                                                numBinsY, numOriBins,
570                                                gaussianWindowSize, valueThreshold), colourspace);
571                        else
572                                dsift = new ColourDenseSIFT(new DenseSIFT(stepX, stepY, binWidth, binHeight, numBinsX, numBinsY,
573                                                numOriBins,
574                                                gaussianWindowSize, valueThreshold), colourspace);
575
576                        dsift.analyseImage(image);
577
578                        if (contrastThreshold <= 0) {
579                                if (byteFeatures)
580                                        return dsift.getByteKeypoints();
581                                return dsift.getFloatKeypoints();
582                        } else {
583                                if (byteFeatures)
584                                        return dsift.getByteKeypoints(contrastThreshold);
585                                return dsift.getFloatKeypoints(contrastThreshold);
586                        }
587                }
588        }
589
590        private static class PyramidDenseSiftMode extends AbstractDenseSiftMode {
591                @Option(
592                                name = "--sizes",
593                                aliases = "-s",
594                                required = true,
595                                usage = "Scales at which the dense SIFT features are extracted. Each value is used as bin size for the DenseSIFT.")
596                List<Integer> sizes = new ArrayList<Integer>();
597
598                @Option(
599                                name = "--magnification-factor",
600                                aliases = "-mf",
601                                usage = "The amount to smooth the image by at each level relative to the bin size (sigma = size/magnification).")
602                float magnificationFactor = 6;
603
604                PyramidDenseSiftMode(LocalFeatureMode mode) {
605                        super(mode);
606                }
607
608                @Override
609                public LocalFeatureList<? extends LocalFeature<?, ?>> extract(byte[] image) throws IOException {
610                        return extractFeature(ImageUtilities.readF(new ByteArrayInputStream(image)));
611                }
612
613                @Override
614                public LocalFeatureList<? extends LocalFeature<?, ?>> extractFeature(MBFImage image) {
615                        return extractFeature(Transforms.calculateIntensityNTSC_LUT(image));
616                }
617
618                protected int[] toArray(List<Integer> in) {
619                        final int[] out = new int[in.size()];
620
621                        for (int i = 0; i < out.length; i++) {
622                                out[i] = in.get(i);
623                        }
624
625                        return out;
626                }
627
628                LocalFeatureList<? extends LocalFeature<?, ?>> extractFeature(FImage image) {
629                        final PyramidDenseSIFT<FImage> dsift;
630
631                        if (approximate)
632                                dsift = new PyramidDenseSIFT<FImage>(new ApproximateDenseSIFT(stepX, stepY, 1, 1, numBinsX, numBinsY,
633                                                numOriBins,
634                                                gaussianWindowSize, valueThreshold), magnificationFactor, toArray(sizes));
635                        else
636                                dsift = new PyramidDenseSIFT<FImage>(new DenseSIFT(stepX, stepY, 1, 1, numBinsX, numBinsY, numOriBins,
637                                                gaussianWindowSize, valueThreshold), magnificationFactor, toArray(sizes));
638
639                        dsift.analyseImage(image);
640
641                        if (contrastThreshold <= 0) {
642                                if (byteFeatures)
643                                        return dsift.getByteKeypoints();
644                                return dsift.getFloatKeypoints();
645                        } else {
646                                if (byteFeatures)
647                                        return dsift.getByteKeypoints(contrastThreshold);
648                                return dsift.getFloatKeypoints(contrastThreshold);
649                        }
650                }
651
652                @Override
653                public Class<? extends LocalFeature<?, ?>> getFeatureClass() {
654                        if (byteFeatures)
655                                return ByteDSIFTKeypoint.class;
656                        return FloatDSIFTKeypoint.class;
657                }
658        }
659
660        private static class PyramidColourDenseSiftMode extends PyramidDenseSiftMode {
661                @Option(name = "--colour-space", aliases = "-cs", required = false, usage = "Specify the colour space")
662                private ColourSpace colourspace = ColourSpace.RGB;
663
664                PyramidColourDenseSiftMode(LocalFeatureMode mode) {
665                        super(mode);
666                }
667
668                @Override
669                public LocalFeatureList<? extends LocalFeature<?, ?>> extract(byte[] image) throws IOException {
670                        return extractFeature(ImageUtilities.readMBF(new ByteArrayInputStream(image)));
671                }
672
673                @Override
674                public LocalFeatureList<? extends LocalFeature<?, ?>> extractFeature(MBFImage image) {
675                        final PyramidDenseSIFT<MBFImage> dsift;
676
677                        if (approximate)
678                                dsift = new PyramidDenseSIFT<MBFImage>(new ColourDenseSIFT(new ApproximateDenseSIFT(stepX, stepY, 1, 1,
679                                                numBinsX,
680                                                numBinsY, numOriBins,
681                                                gaussianWindowSize, valueThreshold), colourspace), magnificationFactor, toArray(sizes));
682                        else
683                                dsift = new PyramidDenseSIFT<MBFImage>(new ColourDenseSIFT(new DenseSIFT(stepX, stepY, 1, 1, numBinsX,
684                                                numBinsY,
685                                                numOriBins,
686                                                gaussianWindowSize, valueThreshold), colourspace), magnificationFactor, toArray(sizes));
687
688                        dsift.analyseImage(image);
689
690                        if (contrastThreshold <= 0) {
691                                if (byteFeatures)
692                                        return dsift.getByteKeypoints();
693                                return dsift.getFloatKeypoints();
694                        } else {
695                                if (byteFeatures)
696                                        return dsift.getByteKeypoints(contrastThreshold);
697                                return dsift.getFloatKeypoints(contrastThreshold);
698                        }
699                }
700        }
701}