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.RankFilters;
013import ij.process.ByteProcessor;
014import ij.process.FloatProcessor;
015import imagingbook.pub.threshold.BackgroundMode;
016import imagingbook.pub.threshold.adaptive.AdaptiveThresholder;
017
018/**
019 * Adaptive thresholder as proposed in J. Sauvola and M. Pietikäinen, 
020 * "Adaptive document image binarization", Pattern Recognition 33(2), 
021 * 1135-1143 (2000).
022 */
023public class SauvolaThresholder extends AdaptiveThresholder {
024        
025        public static class Parameters {
026                public int radius = 15;
027                public double kappa =  0.5;
028                public double sigmaMax =  128;
029                public BackgroundMode bgMode = BackgroundMode.DARK;
030        }
031                
032        private FloatProcessor Imean;
033        private FloatProcessor Isigma;
034        private final Parameters params;
035        
036        public SauvolaThresholder() {
037                super();
038                this.params = new Parameters();
039        }
040        
041        public SauvolaThresholder(Parameters params) {
042                super();
043                this.params = params;
044        }
045        
046        @Override
047        public ByteProcessor getThreshold(ByteProcessor I) {
048                FloatProcessor mean = (FloatProcessor) I.convertToFloat();
049                FloatProcessor var = (FloatProcessor) mean.duplicate();
050                
051                RankFilters rf = new RankFilters();
052                rf.rank(mean, params.radius, RankFilters.MEAN);
053                Imean = mean;
054                
055                rf.rank(var, params.radius, RankFilters.VARIANCE);
056                var.sqrt();
057                Isigma = var;
058                
059                int width = I.getWidth();
060                int height = I.getHeight();
061                final double kappa = params.kappa;
062                final double sigmaMax = params.sigmaMax;
063                final boolean darkBg = (params.bgMode == BackgroundMode.DARK);
064                
065                ByteProcessor Q = new ByteProcessor(width, height);
066                for (int v = 0; v < height; v++) {
067                        for (int u = 0; u < width; u++) {
068                                final double sigma = Isigma.getf(u, v);
069                                final double mu = Imean.getf(u, v);
070                                final double diff = kappa * (sigma / sigmaMax - 1);
071                                int q = (int) Math.rint((darkBg) ? mu * (1 - diff) : mu * (1 + diff));
072                                if (q < 0)
073                                        q = 0;
074                                if (q > 255)
075                                        q = 255;
076                                Q.set(u, v, q);
077                        }
078                }
079                return Q;
080        }
081        
082}