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-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009
010package imagingbook.pub.sift.scalespace;
011
012import ij.process.FloatProcessor;
013import imagingbook.pub.sift.filters.GaussianFilter;
014
015/**
016 * Represents a single scale level. Just a special kind of FloatProcessor
017 * with some extra fields and methods.
018 */
019public class ScaleLevel extends FloatProcessor {        // TODO: make IJ independent, use only float arrays
020        
021        private double absoluteScale;   // really needed?
022        
023        // ------------------------------
024        
025        public ScaleLevel(int width, int height, float[] data, double absoluteScale) {
026                super(width, height, data, null);       // constructor of FloatProcessor
027                this.absoluteScale = absoluteScale;
028        }
029        
030        public ScaleLevel(FloatProcessor fp, double absoluteScale) {
031//              this(fp.getWidth(), fp.getHeight(), ((float[]) fp.getPixels()).clone(), absoluteScale);
032                this(fp.getWidth(), fp.getHeight(), getValues(fp, true), absoluteScale);
033        }
034        
035        public ScaleLevel(ScaleLevel level) {
036//              this(level.getWidth(), level.getHeight(), ((float[]) level.getPixels()).clone(), level.absoluteScale);
037                this(level.getWidth(), level.getHeight(), getValues(level, false), level.absoluteScale);
038        }
039        
040        private static float[] getValues(FloatProcessor fp, boolean normalize) {
041                final double minVal = fp.getMin();
042                final double maxVal = fp.getMax();
043                final float offset = (float) -minVal;
044                final float scale = (float) (1.0 / (maxVal - minVal));
045                final float[] values = (float[]) fp.getPixelsCopy();
046                if (normalize) {
047                        for (int i = 0; i < values.length; i++) {
048                                values[i] = (values[i] + offset) * scale; 
049                        }
050                }
051                return values;
052        }
053        
054        // ------------------------------
055
056        public void filterGaussian(double sigma) {
057                GaussianFilter gf = new GaussianFilter(sigma);
058                gf.applyTo(this);
059        }
060        
061        public ScaleLevel duplicate() {
062                return new ScaleLevel(this);
063        }
064
065        public ScaleLevel decimate() {  // returns a 2:1 subsampled copy of this ScaleLevel
066                //IJ.log(" reducing ...");
067                int width1 = this.getWidth();
068                int height1 = this.getHeight();
069                int width2 = width1 / 2;
070                int height2 = height1 / 2;
071                
072                float[] pixels1 = (float[]) this.getPixels();
073                float[] pixels2 = new float[width2*height2];            
074                for (int v2 = 0 ; v2 < height2; v2++) {
075                        int v1 = 2 * v2;
076                        for (int u2 = 0 ; u2 < width2; u2++) {
077                                int u1 = 2 * u2;
078                                pixels2[v2 * width2 + u2] = pixels1[v1 * width1 + u1];
079                        }
080                }
081                return new ScaleLevel(width2, height2, pixels2, absoluteScale);
082        }
083        
084        public ScaleLevel subtract(FloatProcessor B) {
085                // A <-- A-B
086                ScaleLevel A = this.duplicate();
087                float[] pixelsA = (float[]) A.getPixels();
088                float[] pixelsB = (float[]) B.getPixels();
089                for (int i=0; i<pixelsA.length; i++) {
090                        pixelsA[i] = pixelsA[i] - pixelsB[i];
091                }
092                A.setAbsoluteScale(0);
093                return A;
094        }
095        
096        public void setAbsoluteScale(double sigma) {
097                this.absoluteScale = sigma;
098        }
099        
100        public double getAbsoluteScale() {
101                return absoluteScale;
102        }
103        
104        /*
105         * Collects and returns the 3x3 neighborhood values at this scale level 
106         * at center position u,v. The result is stored in the given 3x3 array nh.
107         */
108        public void get3x3Neighborhood(final int u, final int v, final float[][] nh) {
109                for (int i = 0, x = u - 1; i < 3; i++, x++) {
110                        for (int j = 0, y = v - 1; j < 3; j++, y++) {
111                                nh[i][j] = this.getf(x, y);
112                        }
113                }
114        }
115        
116        public void getGradientPolar(int u, int v, double[] grad) {
117                final double grad_x = this.getf(u+1, v) - this.getf(u-1, v);    // x-component of local gradient
118                final double grad_y = this.getf(u, v+1) - this.getf(u, v-1);    // y-component of local gradient
119                grad[0] = Math.sqrt(grad_x * grad_x + grad_y * grad_y);         // local gradient magnitude (E)
120                grad[1] = Math.atan2(grad_y, grad_x);                                           // local gradient direction (phi)
121        }
122}