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.process.ByteProcessor;
013import imagingbook.pub.threshold.BackgroundMode;
014import imagingbook.pub.threshold.global.OtsuThresholder;
015
016/**
017 * This thresholder splits the image into non-overlapping square
018 * sub-images, computes the optimal threshold within each sub-image 
019 * (using an Otsu thresholder) and interpolates linearly between 
020 * these local thresholds.
021 */
022public class InterpolatingThresholder extends AdaptiveThresholder {
023        
024        public static class Parameters {
025                public int tileSize = 32;
026                public BackgroundMode bgMode = BackgroundMode.DARK;
027        }
028        
029        private final Parameters params;
030        
031        public InterpolatingThresholder() {
032                this.params = new Parameters();
033        }
034        
035        public InterpolatingThresholder(Parameters params) {
036                this.params = params;
037        }
038
039        @Override
040        public ByteProcessor getThreshold(ByteProcessor ip) {
041                final int W = ip.getWidth();
042                final int H = ip.getHeight();
043                final int tileSize = params.tileSize;
044                
045                // determine number of tiles
046                int nW = (W % tileSize == 0) ? (W / tileSize + 1) : (W / tileSize + 2);
047                int nH = (H % tileSize == 0) ? (H / tileSize + 1) : (H / tileSize + 2);
048                
049                int[][] tiles = new int[nW][nH];
050                int s0 = tileSize / 2;  // center of title (s0 + s1 = tileSize)
051//              int s1 = tileSize - s0;
052
053                // compute threshold for each tile
054                int[] h = new int[256];
055                OtsuThresholder thr = new OtsuThresholder();
056
057                int q_ = (params.bgMode == BackgroundMode.DARK) ? 256 : 0;
058
059                for (int j = 0, v0 = 0; j < nH; j++, v0 += tileSize) {
060                        for (int i = 0, u0 = 0; i < nW; i++, u0 += tileSize) {
061                                getSubimageHistogram(ip, u0 - s0, v0 - s0, tileSize, h);
062                                int q = thr.getThreshold(h);
063                                if (q < 0) q = q_; // no threshold found in this tile
064                                tiles[i][j] = q;
065                                //IJ.log(i + "/" + j + ": " + q);
066                        }
067                }
068                
069                ByteProcessor thrIp = new ByteProcessor(W, H);
070                
071                for (int j = 0, v0 = 0; j < nH; j++, v0 += tileSize) {
072                        for (int i = 0, u0 = 0; i < nW; i++, u0 += tileSize) {
073                                // Rectangle re = new Rectangle(u0-s0, v0-s0, u0-s0+tileSize, v0-s0+tileSize);
074                                for (int v = v0 - s0; v < v0 - s0 + tileSize; v++) {
075                                        for (int u = u0 - s0; u < u0 - s0 + tileSize; u++) {
076                                                thrIp.putPixel(u, v, tiles[i][j]);
077                                        }
078                                }
079                        }
080                }
081                
082                // linearly interpolate
083                for (int j = 0, v0 = 0; j < nH - 1; j++, v0 += tileSize) {
084                        for (int i = 0, u0 = 0; i < nW - 1; i++, u0 += tileSize) {
085                                int A = tiles[i][j];
086                                int B = tiles[i + 1][j];
087                                int C = tiles[i][j + 1];
088                                int D = tiles[i + 1][j + 1];
089
090                                // interpolate within [u0, v0, u0 + tileSize, v0 + tileSize]
091                                for (int v = v0; v < v0 + tileSize; v++) {
092                                        double dy = (double) (v - v0) / tileSize;
093                                        double AC = A + dy * (C - A);
094                                        double BD = B + dy * (D - B);
095                                        for (int u = u0; u < u0 + tileSize; u++) {
096                                                double dx = (double) (u - u0) / tileSize;
097                                                double ABCD = AC + dx * (BD - AC);
098                                                // thrIp.putPixel(u,v,tiles[i][j]);
099                                                thrIp.putPixel(u, v, (int) Math.rint(ABCD));
100                                        }
101                                }
102
103                        }
104                }
105                        
106                return thrIp;
107        }
108        
109        private void getSubimageHistogram(ByteProcessor ip, int u0, int v0, int size, int[] h) {
110                for (int i = 0; i < h.length; i++) {
111                        h[i] = 0;
112                }
113                for (int v = v0; v < v0 + size; v++) {
114                        for (int u = u0; u < u0 + size; u++) {
115                                int p = getPaddedPixel(ip, u, v);
116                                h[p]++;
117                        }
118                }
119        }
120
121}