├── __init__.py ├── pythreshold ├── __init__.py ├── global_th │ ├── entropy │ │ ├── __init__.py │ │ ├── pun.py │ │ ├── johannsen.py │ │ └── kapur.py │ ├── __init__.py │ ├── p_tile.py │ ├── two_peaks.py │ ├── min_err.py │ └── otsu.py ├── local_th │ ├── __init__.py │ ├── contrast.py │ ├── bernsen.py │ ├── lmean.py │ ├── bradley_roth.py │ ├── niblack.py │ ├── sauvola.py │ ├── singh.py │ ├── wolf.py │ ├── nick.py │ └── feng.py ├── main.py └── utils.py ├── setup.cfg ├── .gitignore ├── LICENSE ├── setup.py ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pythreshold/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /pythreshold/global_th/entropy/__init__.py: -------------------------------------------------------------------------------- 1 | from .pun import pun_threshold 2 | from .kapur import kapur_threshold, kapur_multithreshold 3 | from .johannsen import johannsen_threshold -------------------------------------------------------------------------------- /pythreshold/global_th/__init__.py: -------------------------------------------------------------------------------- 1 | from .otsu import otsu_threshold, otsu_multithreshold 2 | from .p_tile import p_tile_threshold 3 | from .two_peaks import two_peaks_threshold 4 | from .min_err import min_err_threshold 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignoring pycharm idea folder 2 | .idea/ 3 | 4 | # Ignoring python bytecode files 5 | *.pyc 6 | 7 | # Ignoring MANIFEST file 8 | MANIFEST 9 | 10 | # Ignoring dist directory 11 | dist/ 12 | 13 | # Ignoring build directories 14 | build/ 15 | pythreshold.egg-info/ 16 | 17 | # Ignoring resources directory 18 | resources/ 19 | 20 | # Ignoring TODO file 21 | TODO -------------------------------------------------------------------------------- /pythreshold/local_th/__init__.py: -------------------------------------------------------------------------------- 1 | from .sauvola import sauvola_threshold 2 | from .niblack import niblack_threshold 3 | from .wolf import wolf_threshold 4 | from .nick import nick_threshold 5 | from .lmean import lmean_threshold 6 | from .bradley_roth import bradley_roth_threshold 7 | from .bernsen import bernsen_threshold 8 | from .contrast import contrast_threshold 9 | from .singh import singh_threshold 10 | from .feng import feng_threshold -------------------------------------------------------------------------------- /pythreshold/main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from os.path import basename 4 | 5 | import cv2 6 | 7 | from .utils import test_thresholds 8 | 9 | 10 | def test_thresholds_main(): 11 | """Main entry point for the test thresholds script""" 12 | 13 | # Parsing arguments 14 | ap = argparse.ArgumentParser() 15 | ap.add_argument("-i", "--image", required=True, help="Input image") 16 | ap.add_argument("-o", "--out_dir", required=True, help="Output directory") 17 | args = ap.parse_args() 18 | 19 | # Reading image 20 | img = cv2.imread(args.image, 0) 21 | 22 | if img is None: 23 | print("Invalid input image") 24 | return 25 | 26 | img_name = basename(args.image).split(".")[0] 27 | 28 | test_thresholds(img, args.out_dir, img_name) 29 | -------------------------------------------------------------------------------- /pythreshold/global_th/p_tile.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def p_tile_threshold(image, pct): 10 | """Runs the p-tile threshold algorithm. 11 | 12 | Reference: 13 | Parker, J. R. (2010). Algorithms for image processing and 14 | computer vision. John Wiley & Sons. 15 | 16 | @param image: The input image 17 | @type image: ndarray 18 | @param pct: The percent of desired background pixels (black pixels). 19 | It must lie in the interval [0, 1] 20 | @type pct: float 21 | 22 | @return: The p-tile global threshold 23 | @rtype int 24 | """ 25 | n_pixels = pct * image.shape[0] * image.shape[1] 26 | hist = np.histogram(image, bins=range(256))[0] 27 | hist = np.cumsum(hist) 28 | 29 | return np.argmin(np.abs(hist - n_pixels)) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019, Manuel Aguado Martínez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /pythreshold/local_th/contrast.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | from skimage.util.shape import view_as_windows 6 | 7 | __copyright__ = 'Copyright 2015' 8 | __author__ = u'BSc. Manuel Aguado Martínez' 9 | 10 | 11 | def contrast_threshold(img, w_size=15): 12 | """Runs the contrast thresholding algorithm 13 | 14 | Reference: 15 | Parker, J. R. (2010). Algorithms for image processing and 16 | computer vision. John Wiley & Sons. 17 | 18 | @param img: The input image. Must be a gray scale image 19 | @type img: ndarray 20 | @param w_size: The size of the local window to compute 21 | each pixel threshold. Should be an odd window. 22 | @type w_size: int 23 | 24 | @return: The estimated local threshold for each pixel 25 | @rtype: ndarray 26 | """ 27 | thresholds = np.zeros(img.shape) 28 | 29 | # Obtaining windows 30 | hw_size = w_size // 2 31 | padded_img = np.ones((img.shape[0] + w_size - 1, 32 | img.shape[1] + w_size - 1)) * np.nan 33 | padded_img[hw_size: -hw_size, hw_size: -hw_size] = img 34 | 35 | winds = view_as_windows(padded_img, (w_size, w_size)) 36 | 37 | # Obtaining maximums and minimums 38 | mins = np.nanmin(winds, axis=(2, 3)) 39 | maxs = np.nanmax(winds, axis=(2, 3)) 40 | 41 | min_dif = img - mins 42 | max_dif = maxs - img 43 | 44 | thresholds[min_dif <= max_dif] = 256 45 | thresholds[min_dif > max_dif] = 0 46 | 47 | return thresholds 48 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | # Get the long description from the README file 6 | with open('README.md') as f: 7 | readme = f.read() 8 | 9 | setup( 10 | # Package name 11 | name='pythreshold', 12 | 13 | # Package version 14 | version='0.3.1', 15 | 16 | # Included packages 17 | packages=find_packages(), 18 | 19 | # Package author information 20 | author=u'BSc. Manuel Aguado Martínez', 21 | author_email='manuelaguadomtz@gmail.com', 22 | 23 | # Repository URL 24 | url='https://github.com/manuelaguadomtz/pythreshold', 25 | 26 | entry_points={ 27 | 'console_scripts': [ 28 | 'pythreshold = pythreshold.main:test_thresholds_main', 29 | ], 30 | }, 31 | 32 | # Package requirements 33 | install_requires=[ 34 | 'numpy', 35 | 'scipy', 36 | 'scikit-image', 37 | 'matplotlib', 38 | 'opencv-python' 39 | ], 40 | 41 | # Package description 42 | description='Numpy/Scipy implementations of state-of-the-art image' 43 | ' thresholding algorithms', 44 | long_description=readme, 45 | long_description_content_type='text/markdown', 46 | keywords='thresholding entropy', 47 | 48 | classifiers=[ 49 | 'Intended Audience :: Science/Research', 50 | 'Operating System :: OS Independent', 51 | 'Topic :: Scientific/Engineering', 52 | 'Programming Language :: Python', 53 | 'License :: OSI Approved :: MIT License' 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /pythreshold/global_th/entropy/pun.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def pun_threshold(image): 10 | """ Runs the Pun's threshold algorithm. 11 | 12 | Reference: 13 | Pun, T. ‘‘A New Method for Grey-Level Picture Thresholding Using the 14 | Entropy of the Histogram,’’ Signal Processing 2, no. 3 (1980): 223–237. 15 | 16 | @param image: The input image 17 | @type image: ndarray 18 | 19 | @return: The estimated threshold 20 | @rtype: int 21 | """ 22 | # Calculating histogram 23 | hist, _ = np.histogram(image, bins=range(256), density=True) 24 | 25 | # Calculating histogram cumulative sum 26 | hcs = np.cumsum(hist) 27 | hcs[hcs <= 0] = 1 # To avoid log invalid calculations 28 | 29 | # Calculating inverted histogram cumulative sum 30 | i_hcs = 1.0 - hcs 31 | i_hcs[i_hcs <= 0] = 1 # To avoid log invalid calculations 32 | 33 | # Calculating normed entropy cumulative sum 34 | ecs_norm = np.cumsum(hist * np.log(hist + (hist <= 0))) 35 | ecs_norm /= ecs_norm[-1] 36 | 37 | max_entropy = 0 38 | threshold = 0 39 | for t in range(len(hist) - 1): 40 | black_max = np.max(hist[:t + 1]) 41 | white_max = np.max(hist[t + 1:]) 42 | if black_max * white_max != 0: 43 | x = ecs_norm[t] * np.log(hcs[t]) / np.log(black_max) 44 | y = 1.0 - ecs_norm[t] 45 | z = np.log(i_hcs[t]) / np.log(white_max) 46 | entropy = x + y * z 47 | 48 | if max_entropy < entropy: 49 | max_entropy = entropy 50 | threshold = t 51 | 52 | return threshold 53 | -------------------------------------------------------------------------------- /pythreshold/local_th/bernsen.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | from skimage.util.shape import view_as_windows 6 | 7 | __copyright__ = 'Copyright 2017' 8 | __author__ = u'BSc. Manuel Aguado Martínez' 9 | 10 | 11 | def bernsen_threshold(img, w_size=15, c_thr=30): 12 | """Runs the Bernsen thresholding algorithm 13 | 14 | Reference: 15 | Bernsen, J (1986), "Dynamic Thresholding of Grey-Level Images", 16 | Proc. of the 8th Int. Conf. on Pattern Recognition 17 | 18 | 19 | @param img: The input image. Must be a gray scale image 20 | @type img: ndarray 21 | @param w_size: The size of the local window to compute 22 | each pixel threshold. Should be an odd window. 23 | @type w_size: int 24 | @param c_thr: The threshold contrast to determine an 25 | homogeneous region 26 | @type c_thr: int 27 | 28 | @return: The estimated local threshold for each pixel 29 | @rtype: ndarray 30 | """ 31 | thresholds = np.zeros(img.shape, np.uint8) 32 | 33 | # Obtaining windows 34 | hw_size = w_size // 2 35 | padded_img = np.ones((img.shape[0] + w_size - 1, 36 | img.shape[1] + w_size - 1)) * np.nan 37 | padded_img[hw_size: -hw_size, 38 | hw_size: -hw_size] = img 39 | 40 | winds = view_as_windows(padded_img, (w_size, w_size)) 41 | 42 | mins = np.nanmin(winds, axis=(2, 3)) 43 | maxs = np.nanmax(winds, axis=(2, 3)) 44 | 45 | # Calculating contrast and mid values 46 | contrast = maxs - mins 47 | mid_vals = (maxs + mins) / 2 48 | 49 | thresholds[contrast <= c_thr] = 128 50 | thresholds[contrast > c_thr] = mid_vals[contrast > c_thr] 51 | 52 | return thresholds 53 | -------------------------------------------------------------------------------- /pythreshold/global_th/two_peaks.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | from scipy.ndimage import gaussian_filter 5 | 6 | __copyright__ = 'Copyright 2017' 7 | __author__ = u'BSc. Manuel Aguado Martínez' 8 | 9 | 10 | def two_peaks_threshold(image, smooth_hist=True, sigma=5): 11 | """Runs the two peaks threshold algorithm. It selects two peaks 12 | from the histogram and return the index of the minimum value 13 | between them. 14 | 15 | The first peak is deemed to be the maximum value fo the histogram, 16 | while the algorithm will look for the second peak by multiplying the 17 | histogram values by the square of the distance from the first peak. 18 | This gives preference to peaks that are not close to the maximum. 19 | 20 | Reference: 21 | Parker, J. R. (2010). Algorithms for image processing and 22 | computer vision. John Wiley & Sons. 23 | 24 | @param image: The input image 25 | @type image: ndarray 26 | @param smooth_hist: Indicates whether to smooth the input image 27 | histogram before finding peaks. 28 | @type smooth_hist: bool 29 | @param sigma: The sigma value for the gaussian function used to 30 | smooth the histogram. 31 | @type sigma: int 32 | 33 | @return: The threshold between the two founded peaks with the 34 | minimum histogram value 35 | @rtype: int 36 | """ 37 | hist = np.histogram(image, bins=range(256))[0].astype(np.float) 38 | 39 | if smooth_hist: 40 | hist = gaussian_filter(hist, sigma=sigma) 41 | 42 | f_peak = np.argmax(hist) 43 | 44 | # finding second peak 45 | s_peak = np.argmax((np.arange(len(hist)) - f_peak) ** 2 * hist) 46 | 47 | thr = np.argmin(hist[min(f_peak, s_peak): max(f_peak, s_peak)]) 48 | thr += min(f_peak, s_peak) 49 | 50 | return thr 51 | -------------------------------------------------------------------------------- /pythreshold/local_th/lmean.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def lmean_threshold(img, w_size=15): 10 | """ Runs the local mean thresholding algorithm. 11 | 12 | Reference: 13 | Parker, J. R. (2010). Algorithms for image processing and 14 | computer vision. John Wiley & Sons. 15 | 16 | Modifications: Using integral images to compute local mean. 17 | 18 | @param img: The input image 19 | @type img: ndarray 20 | @param w_size: The size of the local window to compute 21 | each pixel threshold. Should be and odd value 22 | @type w_size: int 23 | 24 | @return: The estimated local threshold for each pixel 25 | @rtype: ndarray 26 | """ 27 | # Obtaining rows and cols 28 | rows, cols = img.shape 29 | i_rows, i_cols = rows + 1, cols + 1 30 | 31 | # Computing integral image 32 | # Leaving first row and column in zero for convenience 33 | integ = np.zeros((i_rows, i_cols), np.float) 34 | 35 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 36 | 37 | # Defining grid 38 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 39 | 40 | # Obtaining local coordinates 41 | hw_size = w_size // 2 42 | x1 = (x - hw_size).clip(1, cols) 43 | x2 = (x + hw_size).clip(1, cols) 44 | y1 = (y - hw_size).clip(1, rows) 45 | y2 = (y + hw_size).clip(1, rows) 46 | 47 | # Obtaining local areas size 48 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 49 | 50 | # Computing sums 51 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 52 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 53 | 54 | # Computing local means 55 | means = sums / l_size 56 | 57 | return means 58 | -------------------------------------------------------------------------------- /pythreshold/global_th/entropy/johannsen.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def johannsen_threshold(image): 10 | """ Runs the Johannsen's threshold algorithm. 11 | 12 | Reference: 13 | Johannsen, G., and J. Bille ‘‘A Threshold Selection Method Using 14 | Information Measures,’’ Proceedings of the Sixth International Conference 15 | on Pattern Recognition, Munich, Germany (1982): 140–143. 16 | 17 | @param image: The input image 18 | @type image: ndarray 19 | 20 | @return: The estimated threshold 21 | @rtype: int 22 | """ 23 | hist, _ = np.histogram(image, bins=range(256), density=True) 24 | 25 | # Knowing the number of leading zeros 26 | l_zeros = 0 27 | while hist[l_zeros] == 0: 28 | l_zeros += 1 29 | 30 | # Truncating histogram to ignore leading and trailing zeros 31 | hist = np.trim_zeros(hist) 32 | 33 | c_hist = hist.cumsum() 34 | ic_hist = 1.0 - c_hist 35 | 36 | # To avoid 0 invalid operations 37 | c_hist[c_hist <= 0] = 1 38 | hist[hist <= 0] = 1 39 | ic_hist[ic_hist <= 0] = 1 40 | 41 | # Obtaining shifted cumulative histograms 42 | sc_hist = np.ones_like(c_hist) 43 | sc_hist[1:] = c_hist[:-1] 44 | 45 | si_chist = np.ones_like(c_hist) 46 | si_chist[1:] = ic_hist[:-1] 47 | 48 | # Obtaining histogram entropy 49 | h_entropy = -hist * np.log(hist) 50 | 51 | # Background entropy 52 | b_entropy = h_entropy - sc_hist * np.log(sc_hist) 53 | s_backg = np.log(c_hist) + b_entropy / c_hist 54 | 55 | # Foreground entropy 56 | f_entropy = h_entropy - ic_hist * np.log(ic_hist) 57 | s_foreg = np.log(si_chist) + f_entropy / si_chist 58 | 59 | return np.argmin((s_foreg + s_backg)[1:-1]) + 1 + l_zeros 60 | -------------------------------------------------------------------------------- /pythreshold/global_th/min_err.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def min_err_threshold(image): 10 | """Runs the minimum error thresholding algorithm. 11 | 12 | Reference: 13 | Kittler, J. and J. Illingworth. ‘‘On Threshold Selection Using Clustering 14 | Criteria,’’ IEEE Transactions on Systems, Man, and Cybernetics 15, no. 5 15 | (1985): 652–655. 16 | 17 | @param image: The input image 18 | @type image: ndarray 19 | 20 | @return: The threshold that minimize the error 21 | @rtype: int 22 | """ 23 | # Input image histogram 24 | hist = np.histogram(image, bins=range(256))[0].astype(np.float) 25 | 26 | # The number of background pixels for each threshold 27 | w_backg = hist.cumsum() 28 | w_backg[w_backg == 0] = 1 # to avoid divisions by zero 29 | 30 | # The number of foreground pixels for each threshold 31 | w_foreg = w_backg[-1] - w_backg 32 | w_foreg[w_foreg == 0] = 1 # to avoid divisions by zero 33 | 34 | # Cumulative distribution function 35 | cdf = np.cumsum(hist * np.arange(len(hist))) 36 | 37 | # Means (Last term is to avoid divisions by zero) 38 | b_mean = cdf / w_backg 39 | f_mean = (cdf[-1] - cdf) / w_foreg 40 | 41 | # Standard deviations 42 | b_std = ((np.arange(len(hist)) - b_mean)**2 * hist).cumsum() / w_backg 43 | f_std = ((np.arange(len(hist)) - f_mean) ** 2 * hist).cumsum() 44 | f_std = (f_std[-1] - f_std) / w_foreg 45 | 46 | # To avoid log of 0 invalid calculations 47 | b_std[b_std == 0] = 1 48 | f_std[f_std == 0] = 1 49 | 50 | # Estimating error 51 | error_a = w_backg * np.log(b_std) + w_foreg * np.log(f_std) 52 | error_b = w_backg * np.log(w_backg) + w_foreg * np.log(w_foreg) 53 | error = 1 + 2 * error_a - 2 * error_b 54 | 55 | return np.argmin(error) 56 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for Contributing 2 | 3 | First of all, I would like to deeply thank you to take the time to go all the way to the end of this document. Our hope is that if you are here is because you are looking to be part of our community. Please do not be shy. Any contribution, although small, has a high importance for us. This package is intended for the scientific community. It is our strongest believe that science is for everyone, no matter race, gender, credo, and political affiliation. Finally, no matter your decision about contributing, we would like to welcome you to our community. 4 | 5 | ## Contributing 6 | 7 | Please feel free of contributing in any of the following ways: 8 | 9 | - Issues: If you detect a bug, an awkward behavior, a feature that you think may be presented in a better way or make the package easiest to use, we would deeply appreciate you bringing our attention to that by creating an issue. It is strongly recommended that the issues are documented as thoroughly as possible when created. Particularly, if you detect a bug, any details and information that you can provide in order to replicate the buggy behavior will be very helpful. Please, if you only have a doubt about the use of the package, do not create an issue, just write us an email and your questions will be answered as soon as possible. 10 | 11 | - Pull requests: If you like to make direct contributions do not refrain your self, we are happy and open to any code modification or suggestion. However, we extremely encourage you to document very well any modifications and to follow the guidelines established by the Python Enhancement Proposals, particularly, the ones in the PEP8. 12 | 13 | It is also very important to notice that for any contribution is mandatory to follow our code of conduct. Actions will be taken against any violations of this code. 14 | 15 | To conclude, only rest us to welcome you again to our community and to wish you happy coding!!!! -------------------------------------------------------------------------------- /pythreshold/local_th/bradley_roth.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def bradley_roth_threshold(img, w_size=15, w=0.15): 10 | """ Runs the Bradley-Roth thresholding algorithm. 11 | 12 | Reference: 13 | Bradley, D., & Roth, G. (2007). Adaptive thresholding 14 | using the integral image. Journal of Graphics Tools, 12(2), 13-21. 15 | 16 | @param img: The input image 17 | @type img: ndarray 18 | @param w_size: The size of the local window to compute 19 | each pixel threshold. Should be and odd value 20 | @type w_size: int 21 | @param w: Used to verify is each pixel is 'w' percent lower than 22 | the local average. It should be a normalized value in the 23 | range [0, 1]. 24 | @type w: float 25 | 26 | @return: The estimated local threshold for each pixel 27 | @rtype: ndarray 28 | """ 29 | # Obtaining rows and cols 30 | rows, cols = img.shape 31 | i_rows, i_cols = rows + 1, cols + 1 32 | 33 | # Computing integral image 34 | # Leaving first row and column in zero for convenience 35 | integ = np.zeros((i_rows, i_cols), np.float) 36 | 37 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 38 | 39 | # Defining grid 40 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 41 | 42 | # Obtaining local coordinates 43 | hw_size = w_size // 2 44 | x1 = (x - hw_size).clip(1, cols) 45 | x2 = (x + hw_size).clip(1, cols) 46 | y1 = (y - hw_size).clip(1, rows) 47 | y2 = (y + hw_size).clip(1, rows) 48 | 49 | # Obtaining local areas size 50 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 51 | 52 | # Computing sums 53 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 54 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 55 | 56 | # Computing local means 57 | means = sums / l_size 58 | 59 | return means * (1 - w) 60 | -------------------------------------------------------------------------------- /pythreshold/local_th/niblack.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def niblack_threshold(img, w_size=15, k=-0.2): 10 | """ Runs the niblack's thresholding algorithm. 11 | 12 | Reference: 13 | Niblack, W.: ‘An introduction to digital image 14 | processing’ (Prentice- Hall, Englewood Cliffs, NJ, 1986), pp. 115–116 15 | 16 | Modifications: Using integral images to compute the local mean and 17 | standard deviation 18 | 19 | @param img: The input image 20 | @type img: ndarray 21 | @param w_size: The size of the local window to compute 22 | each pixel threshold. Should be and odd value 23 | @type w_size: int 24 | @param k: Controls the value of the local threshold. Should lie in the 25 | interval [-0.2, -0.1] 26 | @type k: float 27 | 28 | @return: The estimated local threshold for each pixel 29 | @rtype: ndarray 30 | """ 31 | # Obtaining rows and cols 32 | rows, cols = img.shape 33 | i_rows, i_cols = rows + 1, cols + 1 34 | 35 | # Computing integral images 36 | # Leaving first row and column in zero for convenience 37 | integ = np.zeros((i_rows, i_cols), np.float) 38 | sqr_integral = np.zeros((i_rows, i_cols), np.float) 39 | 40 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 41 | sqr_img = np.square(img.astype(np.float)) 42 | sqr_integral[1:, 1:] = np.cumsum(np.cumsum(sqr_img, axis=0), axis=1) 43 | 44 | # Defining grid 45 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 46 | 47 | # Obtaining local coordinates 48 | hw_size = w_size // 2 49 | x1 = (x - hw_size).clip(1, cols) 50 | x2 = (x + hw_size).clip(1, cols) 51 | y1 = (y - hw_size).clip(1, rows) 52 | y2 = (y + hw_size).clip(1, rows) 53 | 54 | # Obtaining local areas size 55 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 56 | 57 | # Computing sums 58 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 59 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 60 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 61 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 62 | 63 | # Computing local means 64 | means = sums / l_size 65 | 66 | # Computing local standard deviation 67 | stds = np.sqrt(sqr_sums / l_size - np.square(means)) 68 | 69 | # Computing thresholds 70 | thresholds = means + k * stds 71 | 72 | return thresholds 73 | -------------------------------------------------------------------------------- /pythreshold/local_th/sauvola.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def sauvola_threshold(img, w_size=15, k=0.35): 10 | """ Runs the sauvola's thresholding algorithm. 11 | 12 | Reference: 13 | Sauvola, J., Seppanen, T., Haapakoski, S., and Pietikainen, M.: 14 | ‘Adaptive document thresholding’. Proc. 4th Int. Conf. on Document 15 | Analysis and Recognition, Ulm Germany, 1997, pp. 147–152. 16 | 17 | Modifications: Using integral images to compute local mean 18 | and standard deviation 19 | 20 | @param img: The input image 21 | @type img: ndarray 22 | @param w_size: The size of the local window to compute 23 | each pixel threshold. Should be and odd value 24 | @type w_size: int 25 | @param k: Controls the value of the local threshold. It lies in the 26 | interval [0.2, 0.5] 27 | @type k: float 28 | 29 | @return: The estimated local threshold for each pixel 30 | @rtype: ndarray 31 | """ 32 | # Obtaining rows and cols 33 | rows, cols = img.shape 34 | i_rows, i_cols = rows + 1, cols + 1 35 | 36 | # Computing integral images 37 | # Leaving first row and column in zero for convenience 38 | integ = np.zeros((i_rows, i_cols), np.float) 39 | sqr_integral = np.zeros((i_rows, i_cols), np.float) 40 | 41 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 42 | sqr_img = np.square(img.astype(np.float)) 43 | sqr_integral[1:, 1:] = np.cumsum(np.cumsum(sqr_img, axis=0), axis=1) 44 | 45 | # Defining grid 46 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 47 | 48 | # Obtaining local coordinates 49 | hw_size = w_size // 2 50 | x1 = (x - hw_size).clip(1, cols) 51 | x2 = (x + hw_size).clip(1, cols) 52 | y1 = (y - hw_size).clip(1, rows) 53 | y2 = (y + hw_size).clip(1, rows) 54 | 55 | # Obtaining local areas size 56 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 57 | 58 | # Computing sums 59 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 60 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 61 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 62 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 63 | 64 | # Computing local means 65 | means = sums / l_size 66 | 67 | # Computing local standard deviation 68 | stds = np.sqrt(sqr_sums / l_size - np.square(means)) 69 | 70 | # Computing thresholds 71 | thresholds = means * (1.0 + k * (stds / 128 - 1.0)) 72 | 73 | return thresholds 74 | -------------------------------------------------------------------------------- /pythreshold/local_th/singh.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | from skimage.util.shape import view_as_windows 6 | 7 | __copyright__ = 'Copyright 2017' 8 | __author__ = u'BSc. Manuel Aguado Martínez' 9 | 10 | 11 | def singh_threshold(img, w_size=15, k=0.85): # 0.35 12 | """ Runs the Singh thresholding algorithm 13 | 14 | Reference: 15 | Singh, O. I., Sinam, T., James, O., & Singh, T. R. (2012). Local contrast 16 | and mean based thresholding technique in image binarization. International 17 | Journal of Computer Applications, 51, 5-10. 18 | 19 | Modifications: Using integral images to compute local mean 20 | and standard deviation 21 | 22 | @param img: The input image 23 | @type img: ndarray 24 | @param w_size: The size of the local window to compute 25 | each pixel threshold. Should be and odd value 26 | @type w_size: int 27 | @param k: Controls the value of the local threshold. It lies in the 28 | interval [0, 1] 29 | @type k: float 30 | 31 | @return: The estimated local threshold for each pixel 32 | @rtype: ndarray 33 | """ 34 | img = img.astype(np.float) / 255 35 | 36 | # Obtaining rows and cols 37 | rows, cols = img.shape 38 | i_rows, i_cols = rows + 1, cols + 1 39 | 40 | # Computing integral images 41 | # Leaving first row and column in zero for convenience 42 | integ = np.zeros((i_rows, i_cols), np.float) 43 | 44 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 45 | 46 | # Defining grid 47 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 48 | 49 | # Obtaining local coordinates 50 | hw_size = w_size // 2 51 | x1 = (x - hw_size).clip(1, cols) 52 | x2 = (x + hw_size).clip(1, cols) 53 | y1 = (y - hw_size).clip(1, rows) 54 | y2 = (y + hw_size).clip(1, rows) 55 | 56 | # Obtaining local areas size 57 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 58 | 59 | # Computing sums 60 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 61 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 62 | 63 | # Computing local means 64 | means = sums / l_size 65 | 66 | # Obtaining windows 67 | padded_img = np.ones((rows + w_size - 1, cols + w_size - 1)) * np.nan 68 | padded_img[hw_size: -hw_size, hw_size: -hw_size] = img 69 | 70 | winds = view_as_windows(padded_img, (w_size, w_size)) 71 | 72 | # Obtaining maximums and minimums 73 | mins = np.nanmin(winds, axis=(2, 3)) 74 | maxs = np.nanmax(winds, axis=(2, 3)) 75 | 76 | # Computing thresholds 77 | thresholds = k * (means + (maxs - mins) * (1 - img)) * 255 78 | 79 | return thresholds 80 | -------------------------------------------------------------------------------- /pythreshold/local_th/wolf.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def wolf_threshold(img, w_size=15, k=0.5): 10 | """ Runs the Wolf's thresholding algorithm. 11 | 12 | Reference: 13 | C. Wolf, J-M. Jolion, “Extraction and Recognition 14 | of Artificial Text in Multimedia Documents”, Pattern Analysis and 15 | Applications, 6(4):309-326, (2003). 16 | 17 | Modifications: Using integral images to compute the local mean and the 18 | standard deviation 19 | 20 | @param img: The input image 21 | @type img: ndarray 22 | @param w_size: The size of the local window to compute 23 | each pixel threshold. Should be and odd value 24 | @type w_size: int 25 | @param k: Controls the value of the local threshold. 26 | @type k: float 27 | 28 | @return: The estimated local threshold for each pixel 29 | @rtype: ndarray 30 | """ 31 | # Obtaining rows and cols 32 | rows, cols = img.shape 33 | i_rows, i_cols = rows + 1, cols + 1 34 | 35 | # Computing integral images 36 | # Leaving first row and column in zero for convenience 37 | integ = np.zeros((i_rows, i_cols), np.float) 38 | sqr_integral = np.zeros((i_rows, i_cols), np.float) 39 | 40 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 41 | sqr_img = np.square(img.astype(np.float)) 42 | sqr_integral[1:, 1:] = np.cumsum(np.cumsum(sqr_img, axis=0), axis=1) 43 | 44 | # Defining grid 45 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 46 | 47 | # Obtaining local coordinates 48 | hw_size = w_size // 2 49 | x1 = (x - hw_size).clip(1, cols) 50 | x2 = (x + hw_size).clip(1, cols) 51 | y1 = (y - hw_size).clip(1, rows) 52 | y2 = (y + hw_size).clip(1, rows) 53 | 54 | # Obtaining local areas size 55 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 56 | 57 | # Computing sums 58 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 59 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 60 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 61 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 62 | 63 | # Computing local means 64 | means = sums / l_size 65 | 66 | # Computing local standard deviation 67 | stds = np.sqrt(sqr_sums / l_size - np.square(means)) 68 | 69 | # Computing min and max values 70 | max_std = np.max(stds) 71 | min_v = np.min(img) 72 | 73 | # Computing thresholds 74 | thresholds = ((1.0 - k) * means + k * min_v + k * stds / 75 | max_std * (means - min_v)) 76 | 77 | return thresholds 78 | -------------------------------------------------------------------------------- /pythreshold/local_th/nick.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | __copyright__ = 'Copyright 2017' 6 | __author__ = u'BSc. Manuel Aguado Martínez' 7 | 8 | 9 | def nick_threshold(img, w_size=15, k=-0.2): 10 | """ Runs the NICK thresholding algorithm. 11 | 12 | Reference: 13 | Khurshid, K., Siddiqi, I., Faure, C., & Vincent, N. 14 | (2009, January). Comparison of Niblack inspired Binarization methods for 15 | ancient documents. In IS&T/SPIE Electronic Imaging (pp. 72470U-72470U). 16 | International Society for Optics and Photonics. 17 | 18 | Modifications: Using integral images to compute the local mean and the 19 | NICK variation of the Niblack standard deviation term 20 | 21 | @param img: The input image 22 | @type img: ndarray 23 | @param w_size: The size of the local window to compute 24 | each pixel threshold. Should be and odd value 25 | @type w_size: int 26 | @param k: Controls the value of the local threshold. Should lie in the 27 | interval [-0.2, -0.1] 28 | @type k: float 29 | 30 | @return: The estimated local threshold for each pixel 31 | @rtype: ndarray 32 | """ 33 | # Obtaining rows and cols 34 | rows, cols = img.shape 35 | i_rows, i_cols = rows + 1, cols + 1 36 | 37 | # Computing integral images 38 | # Leaving first row and column in zero for convenience 39 | integ = np.zeros((i_rows, i_cols), np.float) 40 | sqr_integral = np.zeros((i_rows, i_cols), np.float) 41 | 42 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 43 | sqr_img = np.square(img.astype(np.float)) 44 | sqr_integral[1:, 1:] = np.cumsum(np.cumsum(sqr_img, axis=0), axis=1) 45 | 46 | # Defining grid 47 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 48 | 49 | # Obtaining local coordinates 50 | hw_size = w_size // 2 51 | x1 = (x - hw_size).clip(1, cols) 52 | x2 = (x + hw_size).clip(1, cols) 53 | y1 = (y - hw_size).clip(1, rows) 54 | y2 = (y + hw_size).clip(1, rows) 55 | 56 | # Obtaining local areas size 57 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 58 | 59 | # Computing sums 60 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 61 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 62 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 63 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 64 | 65 | # Computing local means 66 | means = sums / l_size 67 | 68 | # Computing NICK variation of the Niblack term corresponding 69 | # to the standard deviation 70 | nick_stds = np.sqrt((sqr_sums - np.square(means)) / l_size) 71 | 72 | # Computing thresholds 73 | thresholds = means + k * nick_stds 74 | 75 | return thresholds 76 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # PyEER Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at manuelaguadomtz@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /pythreshold/global_th/entropy/kapur.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | from itertools import combinations 5 | 6 | import numpy as np 7 | 8 | __copyright__ = 'Copyright 2020' 9 | __author__ = u'BSc. Manuel Aguado Martínez' 10 | 11 | 12 | def kapur_threshold(image): 13 | """ Runs the Kapur's threshold algorithm. 14 | 15 | Reference: 16 | Kapur, J. N., P. K. Sahoo, and A. K. C.Wong. ‘‘A New Method for Gray-Level 17 | Picture Thresholding Using the Entropy of the Histogram,’’ Computer Vision, 18 | Graphics, and Image Processing 29, no. 3 (1985): 273–285. 19 | 20 | @param image: The input image 21 | @type image: ndarray 22 | 23 | @return: The estimated threshold 24 | @rtype: int 25 | """ 26 | hist, _ = np.histogram(image, bins=range(256), density=True) 27 | c_hist = hist.cumsum() 28 | c_hist_i = 1.0 - c_hist 29 | 30 | # To avoid invalid operations regarding 0 and negative values. 31 | c_hist[c_hist <= 0] = 1 32 | c_hist_i[c_hist_i <= 0] = 1 33 | 34 | c_entropy = (hist * np.log(hist + (hist <= 0))).cumsum() 35 | b_entropy = -c_entropy / c_hist + np.log(c_hist) 36 | 37 | c_entropy_i = c_entropy[-1] - c_entropy 38 | f_entropy = -c_entropy_i / c_hist_i + np.log(c_hist_i) 39 | 40 | return np.argmax(b_entropy + f_entropy) 41 | 42 | 43 | def _get_regions_entropy(hist, c_hist, thresholds): 44 | """Get the total entropy of regions for a given set of thresholds""" 45 | 46 | total_entropy = 0 47 | for i in range(len(thresholds) - 1): 48 | # Thresholds 49 | t1 = thresholds[i] + 1 50 | t2 = thresholds[i + 1] 51 | 52 | # print(thresholds, t1, t2) 53 | 54 | # Cumulative histogram 55 | hc_val = c_hist[t2] - c_hist[t1 - 1] 56 | 57 | # Normalized histogram 58 | h_val = hist[t1:t2 + 1] / hc_val if hc_val > 0 else 1 59 | 60 | # entropy 61 | entropy = -(h_val * np.log(h_val + (h_val <= 0))).sum() 62 | 63 | # Updating total entropy 64 | total_entropy += entropy 65 | 66 | return total_entropy 67 | 68 | 69 | def _get_thresholds(hist, c_hist, nthrs): 70 | """Get the thresholds that maximize the entropy of the regions 71 | 72 | @param hist: The normalized histogram of the image 73 | @type hist: ndarray 74 | @param c_hist: The cummuative normalized histogram of the image 75 | @type c_hist: ndarray 76 | @param nthrs: The number of thresholds 77 | @type nthrs: int 78 | """ 79 | # Thresholds combinations 80 | thr_combinations = combinations(range(255), nthrs) 81 | 82 | max_entropy = 0 83 | opt_thresholds = None 84 | 85 | # Extending histograms for convenience 86 | # hist = np.append([0], hist) 87 | c_hist = np.append(c_hist, [0]) 88 | 89 | for thresholds in thr_combinations: 90 | # Extending thresholds for convenience 91 | e_thresholds = [-1] 92 | e_thresholds.extend(thresholds) 93 | e_thresholds.extend([len(hist) - 1]) 94 | 95 | # Computing regions entropy for the current combination of thresholds 96 | regions_entropy = _get_regions_entropy(hist, c_hist, e_thresholds) 97 | 98 | if regions_entropy > max_entropy: 99 | max_entropy = regions_entropy 100 | opt_thresholds = thresholds 101 | 102 | return opt_thresholds 103 | 104 | 105 | def kapur_multithreshold(image, nthrs): 106 | """ Runs the Kapur's multi-threshold algorithm. 107 | 108 | Reference: 109 | Kapur, J. N., P. K. Sahoo, and A. K. C.Wong. ‘‘A New Method for Gray-Level 110 | Picture Thresholding Using the Entropy of the Histogram,’’ Computer Vision, 111 | Graphics, and Image Processing 29, no. 3 (1985): 273–285. 112 | 113 | @param image: The input image 114 | @type image: ndarray 115 | @param nthrs: The number of thresholds 116 | @type nthrs: int 117 | 118 | @return: The estimated threshold 119 | @rtype: int 120 | """ 121 | # Histogran 122 | hist, _ = np.histogram(image, bins=range(256), density=True) 123 | 124 | # Cumulative histogram 125 | c_hist = hist.cumsum() 126 | 127 | return _get_thresholds(hist, c_hist, nthrs) 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyThreshold 2 | 3 | **PyThreshold** is a python package featuring Numpy/Scipy implementations of state-of-the-art image thresholding algorithms. 4 | 5 | ## Installing 6 | 7 | **PyThreshold** can be easily installed by typing the following command 8 | 9 | pip install pythreshold 10 | 11 | ## Usage 12 | 13 | from pythreshold.utils import test_thresholds 14 | from scipy.misc import ascent 15 | 16 | # Testing all the included thresholding algorithms 17 | test_thresholds() 18 | 19 | # Testing all the included thresholding algorithms using a custom image 20 | img = ascent() 21 | test_thresholds(img) 22 | 23 | Or just type in a terminal: 24 | 25 | pythreshold -i /path/to/input/image -o /output/directory/for/thresholded/images 26 | 27 | ## Included Algorithms 28 | 29 | * Global thresholding 30 | * Parker, J. R. (2010). Algorithms for image processing and 31 | computer vision. John Wiley & Sons. (**Two peaks**) 32 | * Parker, J. R. (2010). Algorithms for image processing and 33 | computer vision. John Wiley & Sons. (**p-tile**) 34 | * Otsu, Nobuyuki. "A threshold selection method from gray-level 35 | histograms." IEEE transactions on systems, man, and cybernetics 36 | 9.1 (1979): 62-66. 37 | * Kittler, J. and J. Illingworth. "On Threshold Selection Using Clustering 38 | Criteria,"" IEEE Transactions on Systems, Man, and Cybernetics 15, no. 5 39 | (1985): 652–655. 40 | * Entropy thresholding 41 | * Johannsen, G., and J. Bille "A Threshold Selection Method Using 42 | Information Measures,"" Proceedings of the Sixth International Conference 43 | on Pattern Recognition, Munich, Germany (1982): 140–143. 44 | * Kapur, J. N., P. K. Sahoo, and A. K. C.Wong. "A New Method for Gray-Level 45 | Picture Thresholding Using the Entropy of the Histogram,"" Computer Vision, 46 | Graphics, and Image Processing 29, no. 3 (1985): 273–285. 47 | * Pun, T. "A New Method for Grey-Level Picture Thresholding Using the 48 | Entropy of the Histogram,"" Signal Processing 2, no. 3 (1980): 223–237. 49 | * Global thresholding (Multi-threshold) 50 | * Liao, Ping-Sung, Tse-Sheng Chen, and Pau-Choo Chung. "A fast algorithm 51 | for multilevel thresholding." J. Inf. Sci. Eng. 17.5 (2001): 713-727. 52 | * Entropy thresholding 53 | * Kapur, J. N., P. K. Sahoo, and A. K. C.Wong. "A New Method for Gray-Level 54 | Picture Thresholding Using the Entropy of the Histogram,"" Computer Vision, 55 | Graphics, and Image Processing 29, no. 3 (1985): 273–285. 56 | * Local thresholding 57 | * Bernsen, J (1986), "Dynamic Thresholding of Grey-Level Images", 58 | Proc. of the 8th Int. Conf. on Pattern Recognition 59 | * Bradley, D., & Roth, G. (2007). Adaptive thresholding 60 | using the integral image. Journal of Graphics Tools, 12(2), 13-21. 61 | * Parker, J. R. (2010). Algorithms for image processing and 62 | computer vision. John Wiley & Sons. (**Contrast thresholding**) 63 | * Meng-Ling Feng and Yap-Peng Tan, "Contrast adaptive thresholding of 64 | low quality document images", IEICE Electron. Express, Vol. 1, No. 65 | 16, pp.501-506, (2004). 66 | * Parker, J. R. (2010). Algorithms for image processing and 67 | computer vision. John Wiley & Sons. (**Local mean thresholding**) 68 | * Niblack, W.: "An introduction to digital image 69 | processing" (Prentice- Hall, Englewood Cliffs, NJ, 1986), pp. 115–116 70 | * Sauvola, J., Seppanen, T., Haapakoski, S., and Pietikainen, M.: 71 | "Adaptive document thresholding". Proc. 4th Int. Conf. on Document 72 | Analysis and Recognition, Ulm Germany, 1997, pp. 147–152. 73 | * Singh, O. I., Sinam, T., James, O., & Singh, T. R. (2012). Local contrast 74 | and mean based thresholding technique in image binarization. International 75 | Journal of Computer Applications, 51, 5-10. 76 | * C. Wolf, J-M. Jolion, "Extraction and Recognition of Artificial Text in 77 | Multimedia Documents", Pattern Analysis and Applications, 6(4):309-326, (2003). 78 | 79 | 80 | ## Additional Information 81 | 82 | Do you find **PyThreshold** useful? You can collaborate with us: 83 | 84 | [GitHub](https://github.com/manuelaguadomtz/pythreshold) 85 | 86 | Additional materials and information can be found at: 87 | 88 | [ResearchGate](https://www.researchgate.net/project/Numpy-Scipy-implementations-of-image-thresholding-algorithms>) 89 | -------------------------------------------------------------------------------- /pythreshold/local_th/feng.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import numpy as np 4 | 5 | from skimage.util.shape import view_as_windows 6 | 7 | __copyright__ = 'Copyright 2017' 8 | __author__ = u'BSc. Manuel Aguado Martínez' 9 | 10 | 11 | def feng_threshold(img, w_size1=15, w_size2=30, 12 | k1=0.15, k2=0.01, alpha1=0.1): 13 | """ Runs the Feng's thresholding algorithm. 14 | 15 | Reference: 16 | Algorithm proposed in: Meng-Ling Feng and Yap-Peng Tan, “Contrast adaptive 17 | thresholding of low quality document images”, IEICE Electron. Express, 18 | Vol. 1, No. 16, pp.501-506, (2004). 19 | 20 | Modifications: Using integral images to compute the local mean and the 21 | standard deviation 22 | 23 | @param img: The input image. Must be a gray scale image 24 | @type img: ndarray 25 | @param w_size1: The size of the primary local window to compute 26 | each pixel threshold. Should be an odd window 27 | @type w_size1: int 28 | @param w_size2: The size of the secondary local window to compute 29 | the dynamic range standard deviation. Should be an odd window 30 | @type w_size2: int 31 | @param k1: Parameter value that lies in the interval [0.15, 0.25]. 32 | @type k1: float 33 | @param k2: Parameter value that lies in the interval [0.01, 0.05]. 34 | @type k2: float 35 | @param alpha1: Parameter value that lies in the interval [0.15, 0.25]. 36 | @type alpha1: float 37 | 38 | @return: The estimated local threshold for each pixel 39 | @rtype: ndarray 40 | """ 41 | # Obtaining rows and cols 42 | rows, cols = img.shape 43 | i_rows, i_cols = rows + 1, cols + 1 44 | 45 | # Computing integral images 46 | # Leaving first row and column in zero for convenience 47 | integ = np.zeros((i_rows, i_cols), np.float) 48 | sqr_integral = np.zeros((i_rows, i_cols), np.float) 49 | 50 | integ[1:, 1:] = np.cumsum(np.cumsum(img.astype(np.float), axis=0), axis=1) 51 | sqr_img = np.square(img.astype(np.float)) 52 | sqr_integral[1:, 1:] = np.cumsum(np.cumsum(sqr_img, axis=0), axis=1) 53 | 54 | # Defining grid 55 | x, y = np.meshgrid(np.arange(1, i_cols), np.arange(1, i_rows)) 56 | 57 | # Obtaining local coordinates 58 | hw_size = w_size1 // 2 59 | x1 = (x - hw_size).clip(1, cols) 60 | x2 = (x + hw_size).clip(1, cols) 61 | y1 = (y - hw_size).clip(1, rows) 62 | y2 = (y + hw_size).clip(1, rows) 63 | 64 | # Obtaining local areas size 65 | l_size = (y2 - y1 + 1) * (x2 - x1 + 1) 66 | 67 | # Computing sums 68 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 69 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 70 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 71 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 72 | 73 | # Computing local means 74 | means = sums / l_size 75 | 76 | # Computing local standard deviation 77 | stds = np.sqrt(sqr_sums / l_size - np.square(means)) 78 | 79 | # Obtaining windows 80 | padded_img = np.ones((rows + w_size1 - 1, cols + w_size1 - 1)) * np.nan 81 | padded_img[hw_size: -hw_size, hw_size: -hw_size] = img 82 | 83 | winds = view_as_windows(padded_img, (w_size1, w_size1)) 84 | 85 | # Obtaining maximums and minimums 86 | mins = np.nanmin(winds, axis=(2, 3)) 87 | 88 | # Obtaining local coordinates for std range calculations 89 | hw_size = w_size2 // 2 90 | x1 = (x - hw_size).clip(1, cols) 91 | x2 = (x + hw_size).clip(1, cols) 92 | y1 = (y - hw_size).clip(1, rows) 93 | y2 = (y + hw_size).clip(1, rows) 94 | 95 | # Obtaining local areas size 96 | l_size = (y2 - y1 + 2) * (x2 - x1 + 2) 97 | 98 | # Computing sums 99 | sums = (integ[y2, x2] - integ[y2, x1 - 1] - 100 | integ[y1 - 1, x2] + integ[y1 - 1, x1 - 1]) 101 | sqr_sums = (sqr_integral[y2, x2] - sqr_integral[y2, x1 - 1] - 102 | sqr_integral[y1 - 1, x2] + sqr_integral[y1 - 1, x1 - 1]) 103 | 104 | # Computing local means2 105 | means2 = sums / l_size 106 | 107 | # Computing standard deviation range 108 | std_ranges = np.sqrt(sqr_sums / l_size - np.square(means2)) 109 | 110 | # Computing normalized standard deviations and extra alpha parameters 111 | n_stds = stds / std_ranges 112 | n_sqr_std = np.square(n_stds) 113 | alpha2 = k1 * n_sqr_std 114 | alpha3 = k2 * n_sqr_std 115 | 116 | thresholds = ((1 - alpha1) * means + alpha2 * n_stds 117 | * (means - mins) + alpha3 * mins) 118 | 119 | return thresholds 120 | -------------------------------------------------------------------------------- /pythreshold/global_th/otsu.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from itertools import combinations 4 | 5 | import numpy as np 6 | 7 | __copyright__ = 'Copyright 2017' 8 | __author__ = u'BSc. Manuel Aguado Martínez' 9 | 10 | 11 | def otsu_threshold(image=None, hist=None): 12 | """ Runs the Otsu threshold algorithm. 13 | 14 | Reference: 15 | Otsu, Nobuyuki. "A threshold selection method from gray-level 16 | histograms." IEEE transactions on systems, man, and cybernetics 17 | 9.1 (1979): 62-66. 18 | 19 | @param image: The input image 20 | @type image: ndarray 21 | @param hist: The input image histogram 22 | @type hist: ndarray 23 | 24 | @return: The Otsu threshold 25 | @rtype int 26 | """ 27 | if image is None and hist is None: 28 | raise ValueError('You must pass as a parameter either' 29 | 'the input image or its histogram') 30 | 31 | # Calculating histogram 32 | if not hist: 33 | hist = np.histogram(image, bins=range(256))[0].astype(np.float) 34 | 35 | cdf_backg = np.cumsum(np.arange(len(hist)) * hist) 36 | w_backg = np.cumsum(hist) # The number of background pixels 37 | w_backg[w_backg == 0] = 1 # To avoid divisions by zero 38 | m_backg = cdf_backg / w_backg # The means 39 | 40 | cdf_foreg = cdf_backg[-1] - cdf_backg 41 | w_foreg = w_backg[-1] - w_backg # The number of foreground pixels 42 | w_foreg[w_foreg == 0] = 1 # To avoid divisions by zero 43 | m_foreg = cdf_foreg / w_foreg # The means 44 | 45 | var_between_classes = w_backg * w_foreg * (m_backg - m_foreg) ** 2 46 | 47 | return np.argmax(var_between_classes) 48 | 49 | 50 | def _get_variance(hist, c_hist, cdf, thresholds): 51 | """Get the total entropy of regions for a given set of thresholds""" 52 | 53 | variance = 0 54 | 55 | for i in range(len(thresholds) - 1): 56 | # Thresholds 57 | t1 = thresholds[i] + 1 58 | t2 = thresholds[i + 1] 59 | 60 | # Cumulative histogram 61 | weight = c_hist[t2] - c_hist[t1 - 1] 62 | 63 | # Region CDF 64 | r_cdf = cdf[t2] - cdf[t1 - 1] 65 | 66 | # Region mean 67 | r_mean = r_cdf / weight if weight != 0 else 0 68 | 69 | variance += weight * r_mean ** 2 70 | 71 | return variance 72 | 73 | 74 | def _get_thresholds(hist, c_hist, cdf, nthrs): 75 | """Get the thresholds that maximize the variance between regions 76 | 77 | @param hist: The normalized histogram of the image 78 | @type hist: ndarray 79 | @param c_hist: The normalized histogram of the image 80 | @type c_hist: ndarray 81 | @param cdf: The cummulative distribution function of the histogram 82 | @type cdf: ndarray 83 | @param nthrs: The number of thresholds 84 | @type nthrs: int 85 | """ 86 | # Thresholds combinations 87 | thr_combinations = combinations(range(255), nthrs) 88 | 89 | max_var = 0 90 | opt_thresholds = None 91 | 92 | # Extending histograms for convenience 93 | c_hist = np.append(c_hist, [0]) 94 | cdf = np.append(cdf, [0]) 95 | 96 | for thresholds in thr_combinations: 97 | # Extending thresholds for convenience 98 | e_thresholds = [-1] 99 | e_thresholds.extend(thresholds) 100 | e_thresholds.extend([len(hist) - 1]) 101 | 102 | # Computing variance for the current combination of thresholds 103 | regions_var = _get_variance(hist, c_hist, cdf, e_thresholds) 104 | 105 | if regions_var > max_var: 106 | max_var = regions_var 107 | opt_thresholds = thresholds 108 | 109 | return opt_thresholds 110 | 111 | 112 | def otsu_multithreshold(image=None, hist=None, nthrs=2): 113 | """ Runs the Otsu's multi-threshold algorithm. 114 | 115 | Reference: 116 | Otsu, Nobuyuki. "A threshold selection method from gray-level 117 | histograms." IEEE transactions on systems, man, and cybernetics 118 | 9.1 (1979): 62-66. 119 | 120 | Liao, Ping-Sung, Tse-Sheng Chen, and Pau-Choo Chung. "A fast algorithm 121 | for multilevel thresholding." J. Inf. Sci. Eng. 17.5 (2001): 713-727. 122 | 123 | @param image: The input image 124 | @type image: ndarray 125 | @param hist: The input image histogram 126 | @type hist: ndarray 127 | @param nthrs: The number of thresholds 128 | @type nthrs: int 129 | 130 | @return: The estimated thresholds 131 | @rtype: int 132 | """ 133 | # Histogran 134 | if image is None and hist is None: 135 | raise ValueError('You must pass as a parameter either' 136 | 'the input image or its histogram') 137 | 138 | # Calculating histogram 139 | if not hist: 140 | hist = np.histogram(image, bins=range(256))[0].astype(np.float) 141 | 142 | # Cumulative histograms 143 | c_hist = np.cumsum(hist) 144 | cdf = np.cumsum(np.arange(len(hist)) * hist) 145 | 146 | return _get_thresholds(hist, c_hist, cdf, nthrs) 147 | -------------------------------------------------------------------------------- /pythreshold/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from timeit import default_timer 4 | from os.path import join 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | import cv2 9 | 10 | from scipy.misc import face 11 | 12 | # Importing global thresholding algorithms 13 | from .global_th import ( 14 | otsu_threshold, 15 | otsu_multithreshold, 16 | p_tile_threshold, 17 | two_peaks_threshold, 18 | min_err_threshold 19 | ) 20 | 21 | # Importing global entropy thresholding algorithms 22 | from .global_th.entropy import ( 23 | pun_threshold, 24 | kapur_threshold, 25 | johannsen_threshold, 26 | kapur_multithreshold 27 | ) 28 | 29 | # Importing local thresholding algorithms 30 | from .local_th import ( 31 | sauvola_threshold, 32 | niblack_threshold, 33 | wolf_threshold, 34 | nick_threshold, 35 | lmean_threshold, 36 | bradley_roth_threshold, 37 | bernsen_threshold, 38 | contrast_threshold, 39 | singh_threshold, 40 | feng_threshold 41 | ) 42 | 43 | 44 | __copyright__ = 'Copyright 2017' 45 | __author__ = u'BSc. Manuel Aguado Martínez' 46 | 47 | 48 | def apply_threshold(img, threshold=128, wp_val=255): 49 | """Obtain a binary image based on a given global threshold or 50 | a set of local thresholds. 51 | 52 | @param img: The input image. 53 | @type img: ndarray 54 | @param threshold: The global or local thresholds corresponding 55 | to each pixel of the image. 56 | @type threshold: Union[int, ndarray] 57 | @param wp_val: The value assigned to foreground pixels (white pixels). 58 | @type wp_val: int 59 | 60 | @return: A binary image. 61 | @rtype: ndarray 62 | """ 63 | return ((img >= threshold) * wp_val).astype(np.uint8) 64 | 65 | 66 | def apply_multithreshold(img, thresholds): 67 | """Obtain a binary image based on a given global threshold or 68 | a set of local thresholds. 69 | 70 | @param img: The input image. 71 | @type img: ndarray 72 | @param thresholds: Global multi-thresholds. 73 | @type threshold: iterable 74 | 75 | @return: The thresholded image. 76 | @rtype: ndarray 77 | """ 78 | # Extending entropy and thresholds for convenience 79 | e_thresholds = [-1] 80 | e_thresholds.extend(thresholds) 81 | 82 | # Threshold image 83 | t_image = np.zeros_like(img) 84 | 85 | for i in range(1, len(e_thresholds)): 86 | t_image[img >= e_thresholds[i]] = i 87 | 88 | wp_val = 255 // len(thresholds) 89 | 90 | return t_image * wp_val 91 | 92 | 93 | def test_thresholds_plt(img=None): 94 | """Runs all the package thresholding algorithms on the input 95 | image with default parameters and plot the results. 96 | 97 | @param img: The input gray scale image 98 | @type img: ndarray 99 | """ 100 | # Loading image if needed 101 | if img is None: 102 | img = face(gray=True) 103 | 104 | # Plotting test image histogram 105 | plt.figure('Histogram') 106 | plt.hist(img.ravel(), range=(0, 255), bins=255) 107 | 108 | # Applying Otsu method 109 | start = default_timer() 110 | th = otsu_threshold(img) 111 | stop = default_timer() 112 | print('========Otsu==========') 113 | print('Threshold: {0}'.format(th)) 114 | print('Execution time: {0}'.format(stop - start)) 115 | print('') 116 | 117 | # Plotting results 118 | plt.figure('Otsu method') 119 | plt.imshow(apply_threshold(img, th), cmap='gray') 120 | 121 | # Applying Otsu multi-threshold method 122 | start = default_timer() 123 | th = otsu_multithreshold(img, nthrs=2) 124 | stop = default_timer() 125 | print('========Otsu multi-threshold==========') 126 | print('Thresholds: {0}'.format(th)) 127 | print('Execution time: {0}'.format(stop - start)) 128 | print('') 129 | 130 | # Plotting results 131 | plt.figure('Otsu multi-threshold method') 132 | plt.imshow(apply_multithreshold(img, th), cmap='gray') 133 | 134 | # Applying p_tile method 135 | start = default_timer() 136 | th = p_tile_threshold(img, 0.5) 137 | stop = default_timer() 138 | print('========P-tile [p=0.5]==========') 139 | print('Threshold: {0}'.format(th)) 140 | print('Execution time: {0}'.format(stop - start)) 141 | print('') 142 | 143 | # Plotting results 144 | plt.figure('p_tile method [pct=0.5]') 145 | plt.imshow(apply_threshold(img, th), cmap='gray') 146 | 147 | # Applying two peaks method 148 | start = default_timer() 149 | th = two_peaks_threshold(img) 150 | stop = default_timer() 151 | print('========Two peaks==========') 152 | print('Threshold: {0}'.format(th)) 153 | print('Execution time: {0}'.format(stop - start)) 154 | print('') 155 | 156 | # Plotting results 157 | plt.figure('Tow peaks method') 158 | plt.imshow(apply_threshold(img, th), cmap='gray') 159 | 160 | # Applying minimum error method 161 | start = default_timer() 162 | th = min_err_threshold(img) 163 | stop = default_timer() 164 | print('========Minimum Error==========') 165 | print('Threshold: {0}'.format(th)) 166 | print('Execution time: {0}'.format(stop - start)) 167 | print('') 168 | 169 | # Plotting results 170 | plt.figure('Minimum error method') 171 | plt.imshow(apply_threshold(img, th), cmap='gray') 172 | 173 | # Applying global entropy Pun method 174 | start = default_timer() 175 | th = pun_threshold(img) 176 | stop = default_timer() 177 | print('========Global entropy Pun==========') 178 | print('Threshold: {0}'.format(th)) 179 | print('Execution time: {0}'.format(stop - start)) 180 | print('') 181 | 182 | # Plotting results 183 | plt.figure('Global entropy Pun method') 184 | plt.imshow(apply_threshold(img, th), cmap='gray') 185 | 186 | # Applying global entropy Kapur method 187 | start = default_timer() 188 | th = kapur_threshold(img) 189 | stop = default_timer() 190 | print('========Global entropy Kapur==========') 191 | print('Threshold: {0}'.format(th)) 192 | print('Execution time: {0}'.format(stop - start)) 193 | print('') 194 | 195 | # Plotting results 196 | plt.figure('Global entropy Kapur method') 197 | plt.imshow(apply_threshold(img, th), cmap='gray') 198 | 199 | # Applying global entropy Kapur multi-trehshold method 200 | start = default_timer() 201 | th = kapur_multithreshold(img, 2) 202 | stop = default_timer() 203 | print('========Global entropy Kapur multi-threshold==========') 204 | print('Threshold: {0}'.format(th)) 205 | print('Execution time: {0}'.format(stop - start)) 206 | print('') 207 | 208 | # Plotting results 209 | plt.figure('Global entropy Kapur multi-threshold method') 210 | plt.imshow(apply_multithreshold(img, th), cmap='gray') 211 | 212 | # Applying global entropy Johannsen method 213 | start = default_timer() 214 | th = johannsen_threshold(img) 215 | stop = default_timer() 216 | print('========Global entropy Johannsen==========') 217 | print('Threshold: {0}'.format(th)) 218 | print('Execution time: {0}'.format(stop - start)) 219 | print('') 220 | 221 | # Plotting results 222 | plt.figure('Global entropy Johannsen method') 223 | plt.imshow(apply_threshold(img, th), cmap='gray') 224 | 225 | # Applying local Sauvola method 226 | start = default_timer() 227 | th = sauvola_threshold(img) 228 | stop = default_timer() 229 | print('========Local Sauvola==========') 230 | print('Execution time: {0}'.format(stop - start)) 231 | print('') 232 | 233 | # Plotting results 234 | plt.figure('Local Sauvola method') 235 | plt.imshow(apply_threshold(img, th), cmap='gray') 236 | 237 | # Applying local Niblack method 238 | start = default_timer() 239 | th = niblack_threshold(img) 240 | stop = default_timer() 241 | print('========Local Niblack==========') 242 | print('Execution time: {0}'.format(stop - start)) 243 | print('') 244 | 245 | # Plotting results 246 | plt.figure('Local Niblack method') 247 | plt.imshow(apply_threshold(img, th), cmap='gray') 248 | 249 | # Applying local Wolf method 250 | start = default_timer() 251 | th = wolf_threshold(img) 252 | stop = default_timer() 253 | print('========Local Wolf==========') 254 | print('Execution time: {0}'.format(stop - start)) 255 | print('') 256 | 257 | # Plotting results 258 | plt.figure('Local Wolf method') 259 | plt.imshow(apply_threshold(img, th), cmap='gray') 260 | 261 | # Applying local NICK method 262 | start = default_timer() 263 | th = nick_threshold(img) 264 | stop = default_timer() 265 | print('========Local NICK==========') 266 | print('Execution time: {0}'.format(stop - start)) 267 | print('') 268 | 269 | # Plotting results 270 | plt.figure('Local NICK method') 271 | plt.imshow(apply_threshold(img, th), cmap='gray') 272 | 273 | # Applying local mean method 274 | start = default_timer() 275 | th = lmean_threshold(img) 276 | stop = default_timer() 277 | print('========Local mean==========') 278 | print('Execution time: {0}'.format(stop - start)) 279 | print('') 280 | 281 | # Plotting results 282 | plt.figure('Local mean method') 283 | plt.imshow(apply_threshold(img, th), cmap='gray') 284 | 285 | # Applying local Bradley-Roth method 286 | start = default_timer() 287 | th = bradley_roth_threshold(img) 288 | stop = default_timer() 289 | print('========Local Bradley-Roth==========') 290 | print('Execution time: {0}'.format(stop - start)) 291 | print('') 292 | 293 | # Plotting results 294 | plt.figure('Local Bradley-Roth method') 295 | plt.imshow(apply_threshold(img, th), cmap='gray') 296 | 297 | # Applying local Bernsen method 298 | start = default_timer() 299 | th = bernsen_threshold(img) 300 | stop = default_timer() 301 | print('========Local Bernsen==========') 302 | print('Execution time: {0}'.format(stop - start)) 303 | print('') 304 | 305 | # Plotting results 306 | plt.figure('Local Bernsen method') 307 | plt.imshow(apply_threshold(img, th), cmap='gray') 308 | 309 | # Applying local contrast method 310 | start = default_timer() 311 | th = contrast_threshold(img) 312 | stop = default_timer() 313 | print('========Local contrast==========') 314 | print('Execution time: {0}'.format(stop - start)) 315 | print('') 316 | 317 | # Plotting results 318 | plt.figure('Local contrast method') 319 | plt.imshow(apply_threshold(img, th), cmap='gray') 320 | 321 | # Applying local Singh method 322 | start = default_timer() 323 | th = singh_threshold(img) 324 | stop = default_timer() 325 | print('========Local Singh==========') 326 | print('Execution time: {0}'.format(stop - start)) 327 | print('') 328 | 329 | # Plotting results 330 | plt.figure('Local Singh method') 331 | plt.imshow(apply_threshold(img, th), cmap='gray') 332 | 333 | # Applying local Feng method 334 | start = default_timer() 335 | th = feng_threshold(img) 336 | stop = default_timer() 337 | print('========Local Feng==========') 338 | print('Execution time: {0}'.format(stop - start)) 339 | print('') 340 | 341 | # Plotting results 342 | plt.figure('Local Feng method') 343 | plt.imshow(apply_threshold(img, th), cmap='gray') 344 | 345 | # Showing plots 346 | plt.show() 347 | 348 | 349 | def test_thresholds(img, odir, basename): 350 | """Runs all the package thresholding algorithms on the input 351 | image with default parameters and plot the results. 352 | 353 | @param img: The input gray scale image 354 | @type img: ndarray 355 | """ 356 | # Applying Otsu method 357 | start = default_timer() 358 | th = otsu_threshold(img) 359 | stop = default_timer() 360 | print('========Otsu==========') 361 | print('Threshold: {0}'.format(th)) 362 | print('Execution time: {0}'.format(stop - start)) 363 | print('') 364 | fname = join(odir, "%s_Otsu.jpg" % basename) 365 | cv2.imwrite(fname, apply_threshold(img, th)) 366 | 367 | # Applying Otsu multithreshold method 368 | start = default_timer() 369 | th = otsu_multithreshold(img, nthrs=2) 370 | stop = default_timer() 371 | print('========Otsu Multithreshold==========') 372 | print('Threshold: {0}'.format(th)) 373 | print('Execution time: {0}'.format(stop - start)) 374 | print('') 375 | fname = join(odir, "%s_OtsuMultiTh.jpg" % basename) 376 | cv2.imwrite(fname, apply_multithreshold(img, th)) 377 | 378 | # Applying p_tile method 379 | start = default_timer() 380 | th = p_tile_threshold(img, 0.5) 381 | stop = default_timer() 382 | print('========P-tile [p=0.5]==========') 383 | print('Threshold: {0}'.format(th)) 384 | print('Execution time: {0}'.format(stop - start)) 385 | print('') 386 | fname = join(odir, "%s_p_tile.jpg" % basename) 387 | cv2.imwrite(fname, apply_threshold(img, th)) 388 | 389 | # Applying two peaks method 390 | start = default_timer() 391 | th = two_peaks_threshold(img) 392 | stop = default_timer() 393 | print('========Two peaks==========') 394 | print('Threshold: {0}'.format(th)) 395 | print('Execution time: {0}'.format(stop - start)) 396 | print('') 397 | fname = join(odir, "%s_2peaks.jpg" % basename) 398 | cv2.imwrite(fname, apply_threshold(img, th)) 399 | 400 | # Applying minimum error method 401 | start = default_timer() 402 | th = min_err_threshold(img) 403 | stop = default_timer() 404 | print('========Minimum Error==========') 405 | print('Threshold: {0}'.format(th)) 406 | print('Execution time: {0}'.format(stop - start)) 407 | print('') 408 | fname = join(odir, "%s_minError.jpg" % basename) 409 | cv2.imwrite(fname, apply_threshold(img, th)) 410 | 411 | # Applying global entropy Pun method 412 | start = default_timer() 413 | th = pun_threshold(img) 414 | stop = default_timer() 415 | print('========Global entropy Pun==========') 416 | print('Threshold: {0}'.format(th)) 417 | print('Execution time: {0}'.format(stop - start)) 418 | print('') 419 | fname = join(odir, "%s_entropyPun.jpg" % basename) 420 | cv2.imwrite(fname, apply_threshold(img, th)) 421 | 422 | # Applying global entropy Kapur method 423 | start = default_timer() 424 | th = kapur_threshold(img) 425 | stop = default_timer() 426 | print('========Global entropy Kapur==========') 427 | print('Threshold: {0}'.format(th)) 428 | print('Execution time: {0}'.format(stop - start)) 429 | print('') 430 | fname = join(odir, "%s_entropyKapur.jpg" % basename) 431 | cv2.imwrite(fname, apply_threshold(img, th)) 432 | 433 | # Applying global entropy Kapur multi-trehshold method 434 | start = default_timer() 435 | th = kapur_multithreshold(img, 2) 436 | stop = default_timer() 437 | print('========Global entropy Kapur multi-trehshold==========') 438 | print('Threshold: {0}'.format(th)) 439 | print('Execution time: {0}'.format(stop - start)) 440 | print('') 441 | fname = join(odir, "%s_entropyKapurMultiTh.jpg" % basename) 442 | cv2.imwrite(fname, apply_multithreshold(img, th)) 443 | 444 | # Applying global entropy Johannsen method 445 | start = default_timer() 446 | th = johannsen_threshold(img) 447 | stop = default_timer() 448 | print('========Global entropy Johannsen==========') 449 | print('Threshold: {0}'.format(th)) 450 | print('Execution time: {0}'.format(stop - start)) 451 | print('') 452 | fname = join(odir, "%s_entropyJohannsen.jpg" % basename) 453 | cv2.imwrite(fname, apply_threshold(img, th)) 454 | 455 | # Applying local Sauvola method 456 | start = default_timer() 457 | th = sauvola_threshold(img) 458 | stop = default_timer() 459 | print('========Local Sauvola==========') 460 | print('Execution time: {0}'.format(stop - start)) 461 | print('') 462 | fname = join(odir, "%s_sauvola.jpg" % basename) 463 | cv2.imwrite(fname, apply_threshold(img, th)) 464 | 465 | # Applying local Niblack method 466 | start = default_timer() 467 | th = niblack_threshold(img) 468 | stop = default_timer() 469 | print('========Local Niblack==========') 470 | print('Execution time: {0}'.format(stop - start)) 471 | print('') 472 | fname = join(odir, "%s_niblack.jpg" % basename) 473 | cv2.imwrite(fname, apply_threshold(img, th)) 474 | 475 | # Applying local Wolf method 476 | start = default_timer() 477 | th = wolf_threshold(img) 478 | stop = default_timer() 479 | print('========Local Wolf==========') 480 | print('Execution time: {0}'.format(stop - start)) 481 | print('') 482 | fname = join(odir, "%s_wolf.jpg" % basename) 483 | cv2.imwrite(fname, apply_threshold(img, th)) 484 | 485 | # Applying local NICK method 486 | start = default_timer() 487 | th = nick_threshold(img) 488 | stop = default_timer() 489 | print('========Local NICK==========') 490 | print('Execution time: {0}'.format(stop - start)) 491 | print('') 492 | fname = join(odir, "%s_nick.jpg" % basename) 493 | cv2.imwrite(fname, apply_threshold(img, th)) 494 | 495 | # Applying local mean method 496 | start = default_timer() 497 | th = lmean_threshold(img) 498 | stop = default_timer() 499 | print('========Local mean==========') 500 | print('Execution time: {0}'.format(stop - start)) 501 | print('') 502 | fname = join(odir, "%s_localMean.jpg" % basename) 503 | cv2.imwrite(fname, apply_threshold(img, th)) 504 | 505 | # Applying local Bradley-Roth method 506 | start = default_timer() 507 | th = bradley_roth_threshold(img) 508 | stop = default_timer() 509 | print('========Local Bradley-Roth==========') 510 | print('Execution time: {0}'.format(stop - start)) 511 | print('') 512 | fname = join(odir, "%s_bradleyRoth.jpg" % basename) 513 | cv2.imwrite(fname, apply_threshold(img, th)) 514 | 515 | # Applying local Bernsen method 516 | start = default_timer() 517 | th = bernsen_threshold(img) 518 | stop = default_timer() 519 | print('========Local Bernsen==========') 520 | print('Execution time: {0}'.format(stop - start)) 521 | print('') 522 | fname = join(odir, "%s_bernsen.jpg" % basename) 523 | cv2.imwrite(fname, apply_threshold(img, th)) 524 | 525 | # Applying local contrast method 526 | start = default_timer() 527 | th = contrast_threshold(img) 528 | stop = default_timer() 529 | print('========Local contrast==========') 530 | print('Execution time: {0}'.format(stop - start)) 531 | print('') 532 | fname = join(odir, "%s_localContrast.jpg" % basename) 533 | cv2.imwrite(fname, apply_threshold(img, th)) 534 | 535 | # Applying local Singh method 536 | start = default_timer() 537 | th = singh_threshold(img) 538 | stop = default_timer() 539 | print('========Local Singh==========') 540 | print('Execution time: {0}'.format(stop - start)) 541 | print('') 542 | fname = join(odir, "%s_singh.jpg" % basename) 543 | cv2.imwrite(fname, apply_threshold(img, th)) 544 | 545 | # Applying local Feng method 546 | start = default_timer() 547 | th = feng_threshold(img) 548 | stop = default_timer() 549 | print('========Local Feng==========') 550 | print('Execution time: {0}'.format(stop - start)) 551 | print('') 552 | fname = join(odir, "%s_feng.jpg" % basename) 553 | cv2.imwrite(fname, apply_threshold(img, th)) 554 | --------------------------------------------------------------------------------