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.scalespace;
011
012import ij.ImageStack;
013import ij.process.FloatProcessor;
014import imagingbook.common.util.LinearContainer;
015import imagingbook.common.util.PrintsToStream;
016
017import java.io.PrintStream;
018import java.util.Locale;
019
020/**
021 * <p>
022 * Represents a single "octave", which is a stack of scale "levels", in a generic hierarchical scale space. See Sec.
023 * 25.1.4. of [1] for details. Basically this is an array with flexible bottom and top index.
024 * </p>
025 * <p>
026 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
027 * (2022).
028 * </p>
029 *
030 * @param <LevelT> the scale level type
031 * @author WB
032 * @version 2022/11/20
033 */
034public abstract class ScaleOctave<LevelT extends ScaleLevel> implements PrintsToStream {
035        
036        final int Q;                    // number of levels per doubling scale factor
037        final int p;                    // octave index
038        final int width, height;
039        final int botLevelIndex, topLevelIndex;
040        final LinearContainer<LevelT> levels;
041        final double sigma_0;
042        
043        ScaleOctave(int p, int Q, int width, int height, int botLevelIndex, int topLevelIndex, double sigma_0) {
044                this.p = p;
045                this.Q = Q;
046                this.width = width;
047                this.height = height;
048                if (botLevelIndex > topLevelIndex) 
049                        throw new IllegalArgumentException("ScaleOctave (constructor): botLevelIndex > topLevelIndex");
050                this.botLevelIndex = botLevelIndex;
051                this.topLevelIndex = topLevelIndex;
052                this.sigma_0 = sigma_0;
053                levels = new LinearContainer<>(botLevelIndex, topLevelIndex);
054        }
055        
056        /**
057         * Returns the index (p) of this scale space octave.
058         * @return the octave index
059         */
060        public int getOctaveIndex() {
061                return p;
062        }
063        
064        /**
065         * Returns the image width of this scale space octave.
066         * @return the image width
067         */
068        public int getWidth() {
069                return width;
070        }
071        
072        /**
073         * Returns the image height of this scale space octave.
074         * @return the image height
075         */
076        public int getHeight() {
077                return height;
078        }
079
080        /**
081         * Returns a reference to the scale level of this octave with the specified index.
082         *
083         * @param q the level index
084         * @return a reference to scale level q
085         */
086        public LevelT getLevel(int q) { // TODO: honor bottom level, check q
087                return levels.getElement(q);
088        }
089        
090        // for internal use only
091        void setLevel(int q, LevelT level) {    // TODO: check q
092                levels.setElement(q, level);
093        }
094
095        /**
096         * Returns true iff q is outside the level range and position (u,v) is inside the level's bounds.
097         *
098         * @param q the level index
099         * @param u horizontal coordinate
100         * @param v vertical coordinate
101         * @return true iff inside this level
102         */
103        public boolean isInside(int q, int u, int v) {
104                return (botLevelIndex < q && q < topLevelIndex &&
105                                0 < u && u < width-1 && 
106                                0 < v && v < height-1);
107        }
108        
109        /**
110         * Returns the absolute scale (&sigma;) associated with level q.
111         * @param q the level index
112         * @return the absolute scale of the level
113         */
114        public double getAbsoluteScale(int q) {
115                return getLevel(q).getAbsoluteScale();
116        }
117
118        /**
119         * Returns the bottom level index for this scale space octave (e.g., this is -1 for the Gaussian scale space used in
120         * SIFT).
121         *
122         * @return the bottom level index
123         */
124        int getBottomLevelIndex() {
125                return botLevelIndex;
126        }
127
128        /**
129         * Returns the top level index for this scale space octave (e.g., this is Q+1 for the Gaussian scale space used in
130         * SIFT).
131         *
132         * @return the top level index
133         */
134        int getTopLevelIndex() {
135                return topLevelIndex;
136        }
137
138        /**
139         * Returns the absolute scale (&sigma;) for octave index p and level index q;
140         *
141         * @param p the octave index
142         * @param q the level index
143         * @return the absolute scale
144         */
145        public double getAbsoluteScale(int p, int q) {
146                double m =  Q * p + q; 
147                double sigma = sigma_0 * Math.pow(2, m/Q);
148                return sigma;
149        }
150
151        /**
152         * Collects and returns the 3x3x3 neighborhood values from this octave at scale level q and center position (u,v).
153         * The result is stored in the supplied 3x3x3 array nh[s][x][y].
154         *
155         * @param q the level index
156         * @param u the horizontal coordinate
157         * @param v the vertical coordinate
158         * @param nh the 3x3x3 neighborhood array to hold the result
159         */
160        public void getNeighborhood(int q, int u, int v, final float[][][] nh) {
161                // nh[s][x][y]
162                for (int s = 0, li = q - 1; s < 3; s++, li++) {
163                        getLevel(li).get3x3Neighborhood(u, v, nh[s]);
164                }
165        }
166        
167        // ---------------------------------------------------------
168
169        /**
170         * Returns an ImageJ {@link ImageStack} for this octave which can be displayed. Frame labels are automatically set.
171         *
172         * @return an {@link ImageStack} for this octave
173         */
174        public ImageStack getImageStack() {
175                ImageStack stk = new ImageStack(width, height);
176                for (int q = botLevelIndex; q <= topLevelIndex; q++) {
177                        ScaleLevel level = getLevel(q);
178                        if (level != null) {
179                                double scale = level.getAbsoluteScale();
180                                String title = String.format(Locale.US, "q=%d, \u03C3=%.4f", q, scale);
181                                FloatProcessor fp = level.toFloatProcessor();
182                                fp.resetMinAndMax();
183                                stk.addSlice(title, fp);
184                        }
185                }
186                return stk;
187        }
188        
189        @Override
190        public void printToStream(PrintStream strm) {
191                strm.println("  Scale Octave p=" + p);
192                for (int q = botLevelIndex; q <= topLevelIndex; q++) {
193                        ScaleLevel level = getLevel(q);
194                        if (level != null) {
195                                double scale = level.getAbsoluteScale();
196                                strm.format(Locale.US, "   level (p=%d, q=%d, scale=%.4f)\n", p, q, scale);
197                        }
198                }
199        }
200        
201}