001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009
010package imagingbook.common.sift;
011
012import imagingbook.common.geometry.basic.Pnt2d;
013import imagingbook.common.math.VectorNorm;
014
015import java.awt.Shape;
016import java.awt.geom.AffineTransform;
017import java.awt.geom.Path2D;
018import java.util.Locale;
019
020/**
021 * <p>
022 * This class defines a SIFT descriptor. See Sec. 25.3 of [1] for more details. Instances are immutable.
023 * </p>
024 * <p>
025 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
026 * (2022).
027 * </p>
028 *
029 * @author WB
030 * @version 2022/11/20
031 */
032public class SiftDescriptor implements Pnt2d, Comparable<SiftDescriptor> {
033        
034        private final double x; // image position
035        private final double y;
036        private final int scaleLevel;
037        private final double scale;
038        private final double magnitude;
039        private final double orientation;
040        private final int[] features;
041        
042        // --------------------------------------------------------------------
043        
044        // Constructor (non-public)
045        SiftDescriptor(double x, double y, double scale, int scaleLevel, double magnitude, double orientation, int[] features) {
046                this.x = x;
047                this.y = y;
048                this.scale = scale;
049                this.scaleLevel = scaleLevel;
050                this.magnitude = magnitude;
051                this.orientation = orientation;
052                this.features = features;
053        }
054        
055        // --------------------------------------------------------------------
056        
057        @Override
058        public double getX() {
059                return x;
060        }
061
062        @Override
063        public double getY() {
064                return y;
065        }
066
067        /**
068         * Returns this descriptor's absolute scale (&sigma;).
069         * 
070         * @return the absolute scale
071         */
072        public double getScale() {
073                return scale;
074        }
075        
076        /**
077         * Returns this descriptor's scale level (scale space octave index p).
078         * 
079         * @return the scale level
080         */
081        public int getScaleLevel() {
082                return scaleLevel;
083        }
084        
085        /**
086         * Returns this descriptor's gradient response magnitude (score).
087         * 
088         * @return the gradient response magnitude
089         */
090        public double getMagnitude() {
091                return magnitude;
092        }
093
094        /**
095         * Returns this descriptor's orientation angle (in radians).
096         * 
097         * @return the orientation angle
098         */
099        public double getOrientation() {
100                return orientation;
101        }
102        
103        /**
104         * Returns this descriptor's feature vector (array of integers).
105         * 
106         * @return the feature vector
107         */
108        public int[] getFeatures() {
109                return features;
110        }
111        
112        // -----------------------------
113        
114        private static final VectorNorm DefaultNorm = VectorNorm.L2.getInstance();
115
116        /**
117         * Calculates and returns the distance between this descriptor's feature vector and another descriptor's feature
118         * vector using the Euclidean distance ({@link imagingbook.common.math.VectorNorm.L2}).
119         *
120         * @param other another {@link SiftDescriptor}
121         * @return the distance
122         */
123        public double getDistance(SiftDescriptor other) {
124                return DefaultNorm.distance(this.features, other.features);
125        }
126
127        /**
128         * Calculates and returns the distance between this descriptor's feature vector and another descriptor's feature
129         * vector using the specified {@link VectorNorm}.
130         *
131         * @param other another {@link SiftDescriptor}
132         * @param norm a {@link VectorNorm} instance
133         * @return the distance
134         */
135        public double getDistance(SiftDescriptor other, VectorNorm norm) {
136                return norm.distance(this.features, other.features);
137        }
138        
139        // -----------------------------
140
141        @Override       // returns an oriented M-shaped polygon centered at (x,y)
142        public Shape getShape(double featureScale) {
143                double d = featureScale * this.getScale();
144                // create M-shaped path around origin oriented along x-axis:
145                Path2D poly = new Path2D.Double();
146                poly.moveTo(0, 0);
147                poly.lineTo(d, d);
148                poly.lineTo(-d, d);
149                poly.lineTo(-d, -d);
150                poly.lineTo(d, -d);
151                poly.closePath();
152                // translate and rotate shape:
153                AffineTransform atf = new AffineTransform();
154                atf.translate(this.getX(), this.getY());
155                atf.rotate(this.getOrientation());
156                return atf.createTransformedShape(poly);
157        }
158        
159        // -----------------------------
160        
161        @Override
162        public String toString() {
163                return String.format(Locale.US, 
164                                "%s[x=%.1f y=%.1f scale=%.2f mag=%.4f angle=%.2f]", 
165                                getClass().getSimpleName(), x, y, scale, magnitude, orientation);
166        }
167
168        
169        @Override // for sorting SIFT descriptors by descreasing magnitude
170        public int compareTo(SiftDescriptor other) {
171                return Double.compare(other.magnitude, this.magnitude);
172        }
173
174}