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.threshold.adaptive;
011
012import ij.plugin.filter.GaussianBlur;
013import ij.plugin.filter.RankFilters;
014import ij.process.ByteProcessor;
015import ij.process.FloatProcessor;
016import imagingbook.pub.threshold.BackgroundMode;
017
018/**
019 * This version of Niblack's thresholder uses a circular support region, implemented 
020 * with IJ's rank-filter methods.
021 */
022public abstract class NiblackThresholder extends AdaptiveThresholder {
023        
024        public static class Parameters {
025                public int radius = 15;
026                public double kappa =  0.30;
027                public int dMin = 5;
028                public BackgroundMode bgMode = BackgroundMode.DARK;
029        }
030        
031        private final Parameters params;
032        protected FloatProcessor Imean;
033        protected FloatProcessor Isigma;
034
035        private NiblackThresholder () {
036                super();
037                this.params = new Parameters();
038        }
039
040        private NiblackThresholder (Parameters params) {
041                super();
042                this.params = params;
043        }
044        
045        protected abstract void makeMeanAndVariance(ByteProcessor I, Parameters params);
046        
047        @Override
048        public ByteProcessor getThreshold(ByteProcessor I) {
049                int w = I.getWidth();
050                int h = I.getHeight();
051                makeMeanAndVariance(I, params);
052                ByteProcessor Q = new ByteProcessor(w, h);
053                final double kappa = params.kappa;
054                final int dMin = params.dMin;
055                final boolean darkBg = (params.bgMode == BackgroundMode.DARK);
056                
057                for (int v = 0; v < h; v++) {
058                        for (int u = 0; u < w; u++) {
059                                double sigma = Isigma.getf(u, v);
060                                double mu = Imean.getf(u, v);
061                                double diff = kappa * sigma + dMin;
062                                int q = (int) Math.rint((darkBg) ? mu + diff : mu - diff);
063                                if (q < 0)       q = 0;
064                                if (q > 255) q = 255;
065                                Q.set(u, v, q);
066                        }
067                }
068                return Q;
069        }
070        
071        // -----------------------------------------------------------------------
072        
073        public static class Box extends NiblackThresholder {
074
075                public Box() {
076                        super();
077                }
078                
079                public Box(Parameters params) {
080                        super(params);
081                }
082
083                @Override
084                protected void makeMeanAndVariance(ByteProcessor I, Parameters params) {
085                        int width = I.getWidth();
086                        int height = I.getHeight();
087                        Imean =  new FloatProcessor(width, height);
088                        Isigma =  new FloatProcessor(width, height);
089                        final int radius = params.radius;
090                        final int n = (radius + 1 + radius) * (radius + 1 + radius);
091
092                        for (int v = 0; v < height; v++) {
093                                for (int u = 0; u < width; u++) {
094                                        long A = 0;     // sum of image values in support region
095                                        long B = 0;     // sum of squared image values in support region
096                                        for (int j = -radius; j <= radius; j++) {
097                                                for (int i = -radius; i <= radius; i++) {
098                                                        int p = getPaddedPixel(I, u + i, v + j); // this is slow!
099                                                        A = A + p;
100                                                        B = B + p * p;
101                                                }
102                                        }
103                                        Imean.setf(u, v, (float) A / n);
104                                        Isigma.setf(u, v, (float) Math.sqrt((B - (double) (A * A) / n) / n));
105                                }
106                        }
107                }
108                
109        }
110        
111        // -----------------------------------------------------------------------
112        
113        public static class Disk extends NiblackThresholder {
114                
115                public Disk() {
116                        super();
117                }
118                
119                public Disk(Parameters params) {
120                        super(params);
121                }
122
123                @Override
124                protected void makeMeanAndVariance(ByteProcessor I, Parameters params) {
125                        FloatProcessor mean = (FloatProcessor) I.convertToFloat();
126                        FloatProcessor var =  (FloatProcessor) mean.duplicate();
127                        
128                        RankFilters rf = new RankFilters();
129                        rf.rank(mean, params.radius, RankFilters.MEAN);
130                        Imean = mean;
131                        
132                        rf.rank(var, params.radius, RankFilters.VARIANCE);
133                        var.sqrt();
134                        Isigma = var;
135                }
136        }
137        
138        // -----------------------------------------------------------------------
139        
140        public static class Gauss extends NiblackThresholder {
141                
142                public Gauss() {
143                        super();
144                }
145                
146                public Gauss(Parameters params) {
147                        super(params);
148                }
149                
150                @Override
151                protected void makeMeanAndVariance(ByteProcessor I, Parameters params) {
152                        // //uses ImageJ's GaussianBlur
153                        // local variance over square of size (size + 1 + size)^2
154                        int width = I.getWidth();
155                        int height = I.getHeight();
156                        
157                        Imean = new FloatProcessor(width,height);
158                        Isigma = new FloatProcessor(width,height);
159
160                        FloatProcessor A = (FloatProcessor) I.convertToFloatProcessor();
161                        FloatProcessor B = (FloatProcessor) I.convertToFloatProcessor();
162                        B.sqr();
163                        
164                        GaussianBlur gb = new GaussianBlur();
165                        double sigma = params.radius * 0.6;     // sigma of Gaussian filter should be approx. 0.6 of the disk's radius
166                        gb.blurFloat(A, sigma, sigma, 0.002);
167                        gb.blurFloat(B, sigma, sigma, 0.002);
168
169                        for (int v = 0; v < height; v++) {
170                                for (int u = 0; u < width; u++) {
171                                        float a = A.getf(u, v);
172                                        float b = B.getf(u, v);
173                                        float sigmaG = (float) Math.sqrt(b - a*a);
174                                        Imean.setf(u, v, a);
175                                        Isigma.setf(u, v, sigmaG);
176                                }
177                        }
178                }
179        }
180
181}