From 86e9d9c851f50642a70dec5404df24c7d99dd919 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Mon, 14 Dec 2020 07:13:19 +0800 Subject: [PATCH] support binarize for ThresholdAlgorithm (#70) Introduce a non-exported trait type BinaryHistogramThreshold to wrap ThresholdAlgorithm. Except for the change of type hierarchy, there are no other behavior changes. --- Project.toml | 2 + src/ImageBinarization.jl | 28 ++--- src/algorithms/balanced.jl | 93 --------------- src/algorithms/entropy.jl | 96 ---------------- src/algorithms/intermodes.jl | 54 --------- src/algorithms/minimum.jl | 54 --------- src/algorithms/minimum_error.jl | 86 -------------- src/algorithms/moments.jl | 93 --------------- src/algorithms/otsu.jl | 95 ---------------- src/algorithms/single_histogram_threshold.jl | 107 ++++++++++++++++++ src/algorithms/unimodal.jl | 73 ------------ src/algorithms/yen.jl | 86 -------------- test/algorithms/balanced.jl | 73 ------------ test/algorithms/entropy.jl | 73 ------------ test/algorithms/intermodes.jl | 73 ------------ test/algorithms/minimum.jl | 73 ------------ test/algorithms/minimum_error.jl | 73 ------------ test/algorithms/moments.jl | 73 ------------ test/algorithms/otsu.jl | 73 ------------ test/algorithms/single_histogram_threshold.jl | 101 +++++++++++++++++ test/algorithms/unimodal.jl | 76 ------------- test/algorithms/yen.jl | 73 ------------ test/runtests.jl | 10 +- 23 files changed, 219 insertions(+), 1419 deletions(-) delete mode 100644 src/algorithms/balanced.jl delete mode 100644 src/algorithms/entropy.jl delete mode 100644 src/algorithms/intermodes.jl delete mode 100644 src/algorithms/minimum.jl delete mode 100644 src/algorithms/minimum_error.jl delete mode 100644 src/algorithms/moments.jl delete mode 100644 src/algorithms/otsu.jl create mode 100644 src/algorithms/single_histogram_threshold.jl delete mode 100644 src/algorithms/unimodal.jl delete mode 100644 src/algorithms/yen.jl delete mode 100644 test/algorithms/balanced.jl delete mode 100644 test/algorithms/entropy.jl delete mode 100644 test/algorithms/intermodes.jl delete mode 100644 test/algorithms/minimum.jl delete mode 100644 test/algorithms/minimum_error.jl delete mode 100644 test/algorithms/moments.jl delete mode 100644 test/algorithms/otsu.jl create mode 100644 test/algorithms/single_histogram_threshold.jl delete mode 100644 test/algorithms/unimodal.jl delete mode 100644 test/algorithms/yen.jl diff --git a/Project.toml b/Project.toml index 9e6f226..ac937bb 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,7 @@ ImageContrastAdjustment = "f332f351-ec65-5f6a-b3d1-319c6670881a" ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] @@ -20,6 +21,7 @@ HistogramThresholding = "0.1, 0.2" ImageContrastAdjustment = "0.1, 0.2, 0.3" ImageCore = "0.8.3" Polynomials = "1.0" +Reexport = "0.2" ReferenceTests = "0.7, 0.8" julia = "1" diff --git a/src/ImageBinarization.jl b/src/ImageBinarization.jl index d589d3a..d9c035e 100644 --- a/src/ImageBinarization.jl +++ b/src/ImageBinarization.jl @@ -4,9 +4,10 @@ using Base.Iterators: repeated using LinearAlgebra using Polynomials using Statistics +using Reexport using ImageContrastAdjustment -using HistogramThresholding +@reexport using HistogramThresholding using ImageCore using ImageCore.MappedArrays @@ -17,6 +18,7 @@ using ColorVectorSpace include("BinarizationAPI/BinarizationAPI.jl") import .BinarizationAPI: AbstractImageBinarizationAlgorithm, binarize, binarize! +import HistogramThresholding: ThresholdAlgorithm include("integral_image.jl") include("util.jl") @@ -24,20 +26,12 @@ include("compat.jl") # Concrete binarization algorithms +# Balanced, Entropy, Intermodes, Minimum, MinimumError, Moments, Otsu, UnimodalRosin, Yen +include("algorithms/single_histogram_threshold.jl") include("algorithms/adaptive_threshold.jl") # AdaptiveThreshold -include("algorithms/balanced.jl") # Balanced -include("algorithms/entropy.jl") # Entropy -include("algorithms/intermodes.jl") # Intermodes -include("algorithms/minimum.jl") # MinimumIntermodes -include("algorithms/minimum_error.jl") # MinimumError -include("algorithms/moments.jl") # Moments include("algorithms/niblack.jl") # Niblack -include("algorithms/otsu.jl") # Otsu include("algorithms/polysegment.jl") # Polysegment include("algorithms/sauvola.jl") # Sauvola -include("algorithms/unimodal.jl") # UnimodalRosin -include("algorithms/yen.jl") # Yen - include("deprecations.jl") @@ -47,17 +41,11 @@ export # Algorithms AdaptiveThreshold, recommend_size, - Balanced, - Entropy, - Intermodes, - MinimumError, - MinimumIntermodes, - Moments, Niblack, - Otsu, Polysegment, Sauvola, - UnimodalRosin, - Yen + + # also reexport algorithms in HistogramThresholding + SingleHistogramThreshold end # module ImageBinarization diff --git a/src/algorithms/balanced.jl b/src/algorithms/balanced.jl deleted file mode 100644 index 83de27f..0000000 --- a/src/algorithms/balanced.jl +++ /dev/null @@ -1,93 +0,0 @@ -@doc raw""" - Balanced <: AbstractImageBinarizationAlgorithm - Balanced() - - binarize([T,] img, f::Balanced) - binarize!([out,] img, f::Balanced) - -Binarizes the image using the balanced histogram thresholding method. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -In balanced histogram thresholding, one interprets a bin as a physical weight -with a mass equal to its occupancy count. The balanced histogram method involves -iterating the following three steps: (1) choose the midpoint bin index as a -"pivot", (2) compute the combined weight to the left and right of the pivot bin -and (3) remove the leftmost bin if the left side is the heaviest, and the -rightmost bin otherwise. The algorithm stops when only a single bin remains. The -last bin determines the sought-after threshold with which the image is -binarized. - -Let ``f_n`` (``n = 1 \ldots N``) denote the number of observations in the ``n``th -bin of the image histogram. The balanced histogram method constructs a sequence -of nested intervals - -```math -[1,N] \cap \mathbb{Z} \supset I_2 \supset I_3 \supset \ldots \supset I_{N-1}, -``` -where for ``k = 2 \ldots N-1`` -```math -I_k = \begin{cases} - I_{k-1} \setminus \{\min \left( I_{k-1} \right) \} &\text{if } \sum_{n = \min \left( I_{k-1} \right)}^{I_m}f_n \gt \sum_{n = I_m + 1}^{ \max \left( I_{k-1} \right)} f_n, \\ - I_{k-1} \setminus \{\max \left( I_{k-1} \right) \} &\text{otherwise}, -\end{cases} -``` -and ``I_m = \lfloor \frac{1}{2}\left( \min \left( I_{k-1} \right) + \max \left( I_{k-1} \right) \right) \rfloor ``. -The final interval ``I_{N-1}`` consists of a single element which is the bin index -corresponding to the desired threshold. - -If one interprets a bin as a physical weight with a mass equal to its occupancy -count, then each step of the algorithm can be conceptualised as removing the -leftmost or rightmost bin to "balance" the resulting histogram on a pivot. The -pivot is defined to be the midpoint between the start and end points of the -interval under consideration. - -If it turns out that the single element in ``I_{N-1}`` equals ``1`` or ``N`` then -the original histogram must have a single peak and the algorithm has failed to -find a suitable threshold. In this case the algorithm will fall back to using -the `UnimodalRosin` method to select the threshold. - - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Balanced()) -``` - -# Reference - -1. “BI-LEVEL IMAGE THRESHOLDING - A Fast Method”, Proceedings of the First International Conference on Bio-inspired Systems and Signal Processing, 2008. Available: [10.5220/0001064300700076](https://doi.org/10.5220/0001064300700076) -""" -struct Balanced <: AbstractImageBinarizationAlgorithm end - -function (f::Balanced)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Balanced(), counts[1:end], edges) - for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Balanced)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/entropy.jl b/src/algorithms/entropy.jl deleted file mode 100644 index 825602a..0000000 --- a/src/algorithms/entropy.jl +++ /dev/null @@ -1,96 +0,0 @@ -@doc raw""" - Entropy <: AbstractImageBinarizationAlgorithm - Entropy() - - binarize([T,] img, f::Entropy) - binarize!([out,] img, f::Entropy) - -An algorithm for finding the binarization threshold value using -the entropy of the image histogram. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -This algorithm uses the entropy of a one-dimensional histogram to produce a threshold -value. - -Let ``f_1, f_2, \ldots, f_I`` be the frequencies in the various bins of the -histogram and ``I`` the number of bins. With ``N = \sum_{i=1}^{I}f_i``, let -``p_i = \frac{f_i}{N}`` (``i = 1, \ldots, I``) denote the probability -distribution of gray levels. From this distribution one derives two additional -distributions. The first defined for discrete values ``1`` to ``s`` and the -other, from ``s+1`` to ``I``. These distributions are - -```math -A: \frac{p_1}{P_s}, \frac{p_2}{P_s}, \ldots, \frac{p_s}{P_s} -\quad \text{and} \quad -B: \frac{p_{s+1}}{1-P_s}, \ldots, \frac{p_n}{1-P_s} -\quad \text{where} \quad -P_s = \sum_{i=1}^{s}p_i. -``` - -The entropies associated with each distribution are as follows: - -```math -H(A) = \ln(P_s) + \frac{H_s}{P_s} -``` -```math -H(B) = \ln(1-P_s) + \frac{H_n-H_s}{1-P_s} -``` -```math -\quad \text{where} \quad -H_s = -\sum_{i=1}^{s}p_i\ln{p_i} -\quad \text{and} \quad -H_n = -\sum_{i=1}^{I}p_i\ln{p_i}. -``` - -Combining these two entropy functions we have - -```math -\psi(s) = \ln(P_s(1-P_s)) + \frac{H_s}{P_s} + \frac{H_n-H_s}{1-P_s}. -``` -Finding the discrete value ``s`` which maximises the function ``\psi(s)`` produces -the sought-after threshold value (i.e. the bin which determines the threshold). - -See Section 4 of [1] for more details on the derivation of the entropy. - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Entropy()) -``` - -# References -1. J. N. Kapur, P. K. Sahoo, and A. K. C. Wong, “A new method for gray-level picture thresholding using the entropy of the histogram,” *Computer Vision, Graphics, and Image Processing*, vol. 29, no. 1, p. 140, Jan. 1985.[doi:10.1016/s0734-189x(85)90156-2](https://doi.org/10.1016/s0734-189x%2885%2990156-2) -""" -struct Entropy <: AbstractImageBinarizationAlgorithm end - -function (f::Entropy)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Entropy(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Entropy)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/intermodes.jl b/src/algorithms/intermodes.jl deleted file mode 100644 index d5be2c4..0000000 --- a/src/algorithms/intermodes.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" - Intermodes <: AbstractImageBinarizationAlgorithm - Intermodes() - - binarize([T,] img, f::AdaptiveThreshold; [window_size]) - binarize!([out,] img, f::AdaptiveThreshold; [window_size]) - - -Under the assumption that the image histogram is bimodal the image histogram is -smoothed using a length-3 mean filter until two modes remain. The binarization -threshold is then set to the average value of the two modes. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Intermodes()) -``` - -## Reference - -1. C. A. Glasbey, “An Analysis of Histogram-Based Thresholding Algorithms,” CVGIP: Graphical Models and Image Processing, vol. 55, no. 6, pp. 532–537, Nov. 1993. [doi:10.1006/cgip.1993.1040](https://doi.org/10.1006/cgip.1993.1040) -""" -struct Intermodes <: AbstractImageBinarizationAlgorithm end - -function (f::Intermodes)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Intermodes(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Intermodes)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/minimum.jl b/src/algorithms/minimum.jl deleted file mode 100644 index 524f996..0000000 --- a/src/algorithms/minimum.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" - MinimumIntermodes <: AbstractImageBinarizationAlgorithm - MinimumIntermodes() - - binarize([T,] img, f::MinimumIntermodes) - binarize!([out,] img, f::MinimumIntermodes) - -Under the assumption that the image histogram is bimodal the histogram is -smoothed using a length-3 mean filter until two modes remain. The binarization -threshold is then set to the minimum value between the two modes. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, MinimumIntermodes()) -``` - -# Reference - -1. C. A. Glasbey, “An Analysis of Histogram-Based Thresholding Algorithms,” *CVGIP: Graphical Models and Image Processing*, vol. 55, no. 6, pp. 532–537, Nov. 1993. [doi:10.1006/cgip.1993.1040](https://doi.org/10.1006/cgip.1993.1040) -2. J. M. S. Prewitt and M. L. Mendelsohn, “THE ANALYSIS OF CELL IMAGES*,” *Annals of the New York Academy of Sciences*, vol. 128, no. 3, pp. 1035–1053, Dec. 2006. [doi:10.1111/j.1749-6632.1965.tb11715.x](https://doi.org/10.1111/j.1749-6632.1965.tb11715.x) -""" -struct MinimumIntermodes <: AbstractImageBinarizationAlgorithm end - -function (f::MinimumIntermodes)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.MinimumIntermodes(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::MinimumIntermodes)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/minimum_error.jl b/src/algorithms/minimum_error.jl deleted file mode 100644 index 03ac1f5..0000000 --- a/src/algorithms/minimum_error.jl +++ /dev/null @@ -1,86 +0,0 @@ -@doc raw""" - MinimumError <: AbstractImageBinarizationAlgorithm - MinimumError() - - binarize([T,] img, f::MinimumError) - binarize!([out,] img, f::MinimumError) - -Under the assumption that the image histogram is a mixture of two Gaussian -distributions the binarization threshold is chosen such that the expected -misclassification error rate is minimised. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the -``i``th bin of the histogram. Then the probability that an observation -belongs to the ``i``th bin is given by ``p_i = \frac{f_i}{N}`` (``i = 1, -\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``. - -The minimum error thresholding method assumes that one can find a threshold -``T`` which partitions the data into two categories, ``C_0`` and ``C_1``, such that -the data can be modelled by a mixture of two Gaussian distribution. Let -```math -P_0(T) = \sum_{i = 1}^T p_i \quad \text{and} \quad P_1(T) = \sum_{i = T+1}^I p_i -``` -denote the cumulative probabilities, -```math -\mu_0(T) = \sum_{i = 1}^T i \frac{p_i}{P_0(T)} \quad \text{and} \quad \mu_1(T) = \sum_{i = T+1}^I i \frac{p_i}{P_1(T)} -``` -denote the means, and -```math -\sigma_0^2(T) = \sum_{i = 1}^T (i-\mu_0(T))^2 \frac{p_i}{P_0(T)} \quad \text{and} \quad \sigma_1^2(T) = \sum_{i = T+1}^I (i-\mu_1(T))^2 \frac{p_i}{P_1(T)} -``` -denote the variances of categories ``C_0`` and ``C_1``, respectively. - -Kittler and Illingworth proposed to use the minimum error criterion function -```math -J(T) = 1 + 2 \left[ P_0(T) \ln \sigma_0(T) + P_1(T) \ln \sigma_1(T) \right] - 2 \left[P_0(T) \ln P_0(T) + P_1(T) \ln P_1(T) \right] -``` -to assess the discreprancy between the mixture of Gaussians implied by a particular threshold ``T``, -and the piecewise-constant probability density function represented by the histogram. -The discrete value ``T`` which minimizes the function ``J(T)`` produces -the sought-after threshold value (i.e. the bin which determines the threshold). - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, MinimumError()) -``` - -# References - -1. J. Kittler and J. Illingworth, “Minimum error thresholding,” Pattern Recognition, vol. 19, no. 1, pp. 41–47, Jan. 1986. [doi:10.1016/0031-3203(86)90030-0](https://doi.org/10.1016/0031-3203%2886%2990030-0) -2. Q.-Z. Ye and P.-E. Danielsson, “On minimum error thresholding and its implementations,” Pattern Recognition Letters, vol. 7, no. 4, pp. 201–206, Apr. 1988. [doi:10.1016/0167-8655(88)90103-1](https://doi.org/10.1016/0167-8655%2888%2990103-1) -""" -struct MinimumError <: AbstractImageBinarizationAlgorithm end - -function (f::MinimumError)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.MinimumError(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::MinimumError)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/moments.jl b/src/algorithms/moments.jl deleted file mode 100644 index 8ea0dbd..0000000 --- a/src/algorithms/moments.jl +++ /dev/null @@ -1,93 +0,0 @@ -@doc raw""" - Moments <: AbstractImageBinarizationAlgorithm - Moments() - - binarize([T,] img, f::Moments) - binarize!([out,] img, f::Moments) - -The following rule determines the binarization threshold: if one assigns all -observations below the threshold to a value z₀ and all observations above the -threshold to a value z₁, then the first three moments of the original histogram -must match the moments of this specially constructed bilevel histogram. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the -``i``th bin of the histogram and ``z_i`` ``(i=1 \ldots I)`` the observed value -associated with the ``i``th bin. Then the probability that an observation ``z_i`` -belongs to the ``i``th bin is given by ``p_i = \frac{f_i}{N}`` (``i = 1, -\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``. - -Moments can be computed from the histogram ``f`` in the following way: - -```math -m_k = \frac{1}{N} \sum_i p_i (z_i)^k \quad k = 0,1,2,3, \ldots. -``` -The principle of moment-preserving thresholding is to select a threshold value, -as well as two representative values ``z_0`` and ``z_1`` (``z_0 < z_1``), -such that if all below-threshold values in ``f`` are replaced by ``z_0`` and -all above-threshold values are replaced by ``z_1``, then this specially constructed -bilevel histogram ``g`` will have the same first three moments as ``f``. - -Concretely, let ``q_0`` and ``q_1`` denote the fractions of observations below -and above the threshold in ``f``, respectively. The constraint that the first -three moments in ``g`` must equal the first three moments in ``f`` can be -expressed by the following system of four equations - -```math -\begin{aligned} - q_0 (z_0)^0 + q_1 (z_1)^0 & = m_0 \\ - q_0 (z_0)^1 + q_1 (z_1)^1 & = m_1 \\ - q_0 (z_0)^2 + q_1 (z_1)^2 & = m_2 \\ - q_0 (z_0)^3 + q_1 (z_1)^3 & = m_3 \\ -\end{aligned} -``` -where the left-hand side represents the moments of ``g`` and the right-hand side -represents the moments of ``f``. To find the desired treshold value, one first solves -the four equations to obtain ``q_0`` and ``q_1``, and then chooses the threshold -``t`` such that ``q_0 = \sum_{z_i \le t} p_i``. - - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Moments()) -``` - -# Reference - -[1] W.-H. Tsai, “Moment-preserving thresolding: A new approach,” Computer Vision, Graphics, and Image Processing, vol. 29, no. 3, pp. 377–393, Mar. 1985. [doi:10.1016/0734-189x(85)90133-1](https://doi.org/10.1016/0734-189x%2885%2990133-1) -""" -struct Moments <: AbstractImageBinarizationAlgorithm end - -function (f::Moments)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Moments(), counts[1:end], edges) - for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Moments)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/otsu.jl b/src/algorithms/otsu.jl deleted file mode 100644 index 7e0bc0e..0000000 --- a/src/algorithms/otsu.jl +++ /dev/null @@ -1,95 +0,0 @@ -@doc raw""" - Otsu <: AbstractImageBinarizationAlgorithm - Otsu() - - binarize([T,] img, f::Otsu) - binarize!([out,] img, f::Otsu) - -Under the assumption that the image histogram is bimodal the binarization -threshold is set so that the resultant between-class variance is maximal. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the -``i``th bin of the image histogram. Then the probability that an observation -belongs to the ``i``th bin is given by ``p_i = \frac{f_i}{N}`` (``i = 1, -\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``. - -The choice of a threshold ``T`` partitions the data into two categories, ``C_0`` -and ``C_1``. Let -```math -P_0(T) = \sum_{i = 1}^T p_i \quad \text{and} \quad P_1(T) = \sum_{i = T+1}^I p_i -``` -denote the cumulative probabilities, -```math -\mu_0(T) = \sum_{i = 1}^T i \frac{p_i}{P_0(T)} \quad \text{and} \quad \mu_1(T) = \sum_{i = T+1}^I i \frac{p_i}{P_1(T)} -``` -denote the means, and -```math -\sigma_0^2(T) = \sum_{i = 1}^T (i-\mu_0(T))^2 \frac{p_i}{P_0(T)} \quad \text{and} \quad \sigma_1^2(T) = \sum_{i = T+1}^I (i-\mu_1(T))^2 \frac{p_i}{P_1(T)} -``` -denote the variances of categories ``C_0`` and ``C_1``, respectively. -Furthermore, let -```math -\mu = P_0(T)\mu_0(T) + P_1(T)\mu_1(T), -``` -represent the overall mean, -```math -\sigma_b^2(T) = P_0(T)(\mu_0(T) - \mu)^2 + P_1(T)(\mu_1(T) - \mu)^2, -``` -the between-category variance, and -```math -\sigma_w^2(T) = P_0(T) \sigma_0^2(T) + P_1(T)\sigma_1^2(T) -``` -the within-category variance, respectively. - -Finding the discrete value ``T`` which maximises the function ``\sigma_b^2(T)`` -produces the sought-after threshold value (i.e. the bin which determines the -threshold). As it turns out, that threshold value is equal to the threshold -decided by minimizing the within-category variances criterion ``\sigma_w^2(T)``. -Furthermore, that threshold is also the same as the threshold calculated by -maximizing the ratio of between-category variance to within-category variance. - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Otsu()) -``` - -# Reference - -1. Nobuyuki Otsu (1979). “A threshold selection method from gray-level histograms”. *IEEE Trans. Sys., Man., Cyber.* 9 (1): 62–66. [doi:10.1109/TSMC.1979.4310076](http://dx.doi.org/doi:10.1109/TSMC.1979.4310076) -""" -struct Otsu <: AbstractImageBinarizationAlgorithm end - -function (f::Otsu)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Otsu(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Otsu)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/single_histogram_threshold.jl b/src/algorithms/single_histogram_threshold.jl new file mode 100644 index 0000000..e4ade2d --- /dev/null +++ b/src/algorithms/single_histogram_threshold.jl @@ -0,0 +1,107 @@ +# Alternatively, we could use `InteractiveUtils.subtypes` to get a list of it. +threshold_methods = ( + Balanced, + Entropy, + Intermodes, + MinimumError, + MinimumIntermodes, + Moments, + Otsu, + UnimodalRosin, + Yen +) + +""" + SingleHistogramThreshold <: AbstractImageBinarizationAlgorithm + SingleHistogramThreshold(alg::ThresholdAlgorithm; nbins=256) + + binarize([T,] img, f::ThresholdAlgorithm; nbins=256) + binarize!([out,] img, f::ThresholdAlgorithm; nbins=256) + +Binarizes the image `img` using the threshold found by given threshold finding algorithm `alg`. + +# Output + +Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If `T` is not specified, it +is inferred from `out` and `img`. + +# Arguments + +The function argument is described in more detail below. + +## `img::AbstractArray` + +The image that needs to be binarized. The image is automatically converted to `Gray` in order to +construct the requisite graylevel histogram. + +## `alg::ThresholdAlgorithm` + +`ThresholdAlgorithm` is an Abstract type defined in `ThresholdAlgorithm.jl`, it provides various +threshold finding algorithms: + +$(mapreduce(x->"- [`"*string(x)*"`](@ref)", (x,y)->x*"\n"*y, threshold_methods)) + +For the more detailed explaination and the construction, please refer to each concrete algorithm. +For example, type `?Otsu` in REPL will give you more details on how to use `Otsu` methods. + +## `nbins::Integer` + +The number of discrete bins that used to build the histogram. A smaller `nbins` could possibly gives +a less noisy, or in other words, a smoother output. The default value is `256`. + +# Examples + +All the usage follows the same pattern, take `Otsu` as an example: + +```julia +using TestImages, ImageBinarization + +img = testimage("cameraman") +img_binary = binarize(img, Otsu()) +``` + +It is less convenient, but still, you could also construct a `SingleHistogramThreshold` by yourself: + +```julia +using TestImages, ImageBinarization + +img = testimage("cameraman") +f = SingleHistogramThreshold(Otsu(), nbins=256) +img_binary = binarize(img, f) +``` + +""" +struct SingleHistogramThreshold{T} <: AbstractImageBinarizationAlgorithm + alg::T + nbins::Int + function SingleHistogramThreshold(alg::T; nbins::Integer) where T <: ThresholdAlgorithm + new{T}(alg, nbins) + end +end + +function binarize!(out::GenericGrayImage, img, f::ThresholdAlgorithm, args...; nbins::Integer=256, kwargs...) + binarize!(out, img, SingleHistogramThreshold(f, nbins=nbins), args...; kwargs...) +end +function binarize!(img::GenericGrayImage, f::ThresholdAlgorithm, args...; nbins::Integer=256, kwargs...) + binarize!(img, SingleHistogramThreshold(f, nbins=nbins), args...; kwargs...) +end +function binarize(::Type{T}, img, f::ThresholdAlgorithm, args...; nbins::Integer=256, kwargs...) where T + binarize(T, img, SingleHistogramThreshold(f, nbins=nbins), args...; kwargs...) +end +function binarize(img, f::ThresholdAlgorithm, args...; nbins::Integer=256, kwargs...) + binarize(ccolor(Gray, eltype(img)), img, SingleHistogramThreshold(f, nbins=nbins), args...; kwargs...) +end +function binarize(img::AbstractArray{T}, f::ThresholdAlgorithm, args...; kwargs...) where T <: Number + # issue #46: Do not promote Number to Gray{<:Number} + binarize(T, img, f, args...; kwargs...) +end + + +function (f::SingleHistogramThreshold)(out::GenericGrayImage, img::GenericGrayImage) + edges, counts = build_histogram(img, f.nbins) + t = find_threshold(f.alg, counts[1:end], edges) + @. out = img > t # here we rely on implicit type conversion to `eltype(out)` +end + +(f::SingleHistogramThreshold)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = + f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/unimodal.jl b/src/algorithms/unimodal.jl deleted file mode 100644 index b6467bd..0000000 --- a/src/algorithms/unimodal.jl +++ /dev/null @@ -1,73 +0,0 @@ -""" - UnimodalRosin <: AbstractImageBinarizationAlgorithm - UnimodalRosin() - - binarize([T,] img, f::UnimodalRosin) - binarize!([out,] img, f::UnimodalRosin) - -Uses Rosin's Unimodal threshold algorithm to binarize the image. - - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - -# Details - -This algorithm first selects the bin in the image histogram with the highest -frequency. The algorithm then searches from the location of the maximum bin to -the last bin of the histogram for the first bin with a frequency of 0 (known as -the minimum bin.). A line is then drawn that passes through both the maximum and -minimum bins. The bin with the greatest orthogonal distance to the line is -chosen as the threshold value. - - -## Assumptions - -This algorithm assumes that: - -* The histogram is unimodal. -* There is always at least one bin that has a frequency of 0. If not, the algorithm will use the last bin as the minimum bin. - -If the histogram includes multiple bins with a frequency of 0, the algorithm -will select the first zero bin as its minimum. If there are multiple bins with -the greatest orthogonal distance, the leftmost bin is selected as the threshold. - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - - -# Example - -Compute the threshold for the "moonsurface" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("moonsurface") -img_binary = binarize(img, UnimodalRosin()) -``` - -# Reference -1. P. L. Rosin, “Unimodal thresholding,” Pattern Recognition, vol. 34, no. 11, pp. 2083–2096, Nov. 2001.[doi:10.1016/s0031-3203(00)00136-9](https://doi.org/10.1016/s0031-3203%2800%2900136-9) -""" -struct UnimodalRosin <: AbstractImageBinarizationAlgorithm end - -function (f::UnimodalRosin)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.UnimodalRosin(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::UnimodalRosin)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/src/algorithms/yen.jl b/src/algorithms/yen.jl deleted file mode 100644 index 077b1c8..0000000 --- a/src/algorithms/yen.jl +++ /dev/null @@ -1,86 +0,0 @@ -@doc raw""" - Yen <: AbstractImageBinarizationAlgorithm - Yen() - - binarize([T,] img, f::Yen) - binarize!([out,] img, f::Yen) - -Computes the binarization threshold value using Yen's maximum correlation criterion for -bilevel thresholding. - -# Output - -Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If -`T` is not specified, it is inferred from `out` and `img`. - - -# Details - -This algorithm uses the concept of *entropic correlation* of a gray level histogram to produce a threshold -value. - -Let ``f_1, f_2, \ldots, f_I`` be the frequencies in the various bins of the -histogram and ``I`` the number of bins. With ``N = \sum_{i=1}^{I}f_i``, let -``p_i = \frac{f_i}{N}`` (``i = 1, \ldots, I``) denote the probability -distribution of gray levels. From this distribution one derives two additional -distributions. The first defined for discrete values ``1`` to ``s`` and the -other, from ``s+1`` to ``I``. These distributions are - -```math -A: \frac{p_1}{P_s}, \frac{p_2}{P_s}, \ldots, \frac{p_s}{P_s} -\quad \text{and} \quad -B: \frac{p_{s+1}}{1-P_s}, \ldots, \frac{p_n}{1-P_s} -\quad \text{where} \quad -P_s = \sum_{i=1}^{s}p_i. -``` -The entropic correlations associated with each distribution are - -```math -C(A) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 \quad \text{and} \quad C(B) = -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2. -``` - -Combining these two entropic correlation functions we have - -```math -\psi(s) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2. -``` -Finding the discrete value ``s`` which maximises the function ``\psi(s)`` produces -the sought-after threshold value (i.e. the bin which determines the threshold). - -# Arguments - -The function argument is described in more detail below. - -## `img::AbstractArray` - -The image that needs to be binarized. The image is automatically converted -to `Gray` in order to construct the requisite graylevel histogram. - -# Example - -Binarize the "cameraman" image in the `TestImages` package. - -```julia -using TestImages, ImageBinarization - -img = testimage("cameraman") -img_binary = binarize(img, Yen()) -``` - -# Reference - -1. Yen JC, Chang FJ, Chang S (1995), “A New Criterion for Automatic Multilevel Thresholding”, IEEE Trans. on Image Processing 4 (3): 370-378, [doi:10.1109/83.366472](https://doi.org/10.1109/83.366472) -""" -struct Yen <: AbstractImageBinarizationAlgorithm end - -function (f::Yen)(out::GenericGrayImage, img::GenericGrayImage) - edges, counts = build_histogram(img, 256) - t = find_threshold(HistogramThresholding.Yen(), counts[1:end], edges) - @simd for i in CartesianIndices(img) - out[i] = img[i] < t ? 0 : 1 - end - out -end - -(f::Yen)(out::GenericGrayImage, img::AbstractArray{<:Color3}) = - f(out, of_eltype(Gray, img)) diff --git a/test/algorithms/balanced.jl b/test/algorithms/balanced.jl deleted file mode 100644 index e129ea4..0000000 --- a/test/algorithms/balanced.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "balanced" begin - @info "Test: Balanced" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Balanced() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Balanced() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Balanced_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Balanced() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Balanced_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Balanced() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/entropy.jl b/test/algorithms/entropy.jl deleted file mode 100644 index 878602f..0000000 --- a/test/algorithms/entropy.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "entropy" begin - @info "Test: Entropy" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Entropy() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Entropy() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Entropy_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Entropy() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Entropy_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Entropy() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/intermodes.jl b/test/algorithms/intermodes.jl deleted file mode 100644 index 5989be3..0000000 --- a/test/algorithms/intermodes.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "intermodes" begin - @info "Test: Intermodes" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Intermodes() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Intermodes() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Intermodes_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Intermodes() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Intermodes_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Intermodes() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/minimum.jl b/test/algorithms/minimum.jl deleted file mode 100644 index 44f88c8..0000000 --- a/test/algorithms/minimum.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "minimum" begin - @info "Test: MinimumIntermodes" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = MinimumIntermodes() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = MinimumIntermodes() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/MinimumIntermodes_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = MinimumIntermodes() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/MinimumIntermodes_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = MinimumIntermodes() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/minimum_error.jl b/test/algorithms/minimum_error.jl deleted file mode 100644 index ff57eb2..0000000 --- a/test/algorithms/minimum_error.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "minimum_error" begin - @info "Test: MinimumError" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = MinimumError() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = MinimumError() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/MinimumError_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = MinimumError() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/MinimumError_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = MinimumError() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/moments.jl b/test/algorithms/moments.jl deleted file mode 100644 index 549493e..0000000 --- a/test/algorithms/moments.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "moments" begin - @info "Test: Moments" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Moments() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Moments() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Moments_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Moments() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Moments_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Moments() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/otsu.jl b/test/algorithms/otsu.jl deleted file mode 100644 index 44ad6d9..0000000 --- a/test/algorithms/otsu.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "otsu" begin - @info "Test: Otsu" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Otsu() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Otsu() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Otsu_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Otsu() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Otsu_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Otsu() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/single_histogram_threshold.jl b/test/algorithms/single_histogram_threshold.jl new file mode 100644 index 0000000..da702f6 --- /dev/null +++ b/test/algorithms/single_histogram_threshold.jl @@ -0,0 +1,101 @@ +@testset "SingleHistogramThreshold" begin + @info "Test: SingleHistogramThreshold" + + threshold_methods = [ + ("Balanced", Balanced()), + ("Entropy", Entropy()), + ("Intermodes", Intermodes()), + ("MinimumError", MinimumError()), + ("MinimumIntermodes", MinimumIntermodes()), + ("Moments", Moments()), + ("Otsu", Otsu()), + ("UnimodalRosin", UnimodalRosin()), + ("Yen", Yen()), + ] + + @testset "API" begin + img_gray = imresize(testimage("cameraman"); ratio=0.25) + img = copy(img_gray) + + for (fname, f) in threshold_methods + binarized_img = binarize(img, f) + @test img == img_gray # img unchanged + @test eltype(binarized_img) == Gray{N0f8} + + ref = copy(binarized_img) + + g = SingleHistogramThreshold(f, nbins=256) + binarized_img = binarize(img, g) + @test ref == binarized_img + @test eltype(binarized_img) == Gray{N0f8} + + for alg in [f, g] + binarized_img = binarize(Gray{Bool}, img, alg) + @test img == img_gray # img unchanged + @test eltype(binarized_img) == Gray{Bool} + @test ref == binarized_img + + binarized_img = similar(img, Bool) + binarize!(binarized_img, img, alg) + @test img == img_gray # img unchanged + @test eltype(binarized_img) == Bool + @test ref == binarized_img + + binarized_img = copy(img_gray) + binarize!(binarized_img, alg) + @test eltype(binarized_img) == Gray{N0f8} + @test ref == binarized_img + end + + # binarize for SingleHistogramThreshold does not accept keyword `nbins` + binarized_img = binarize(img, f, nbins=256) + @test img == img_gray + @test eltype(binarized_img) == Gray{N0f8} + @test ref == binarized_img + + for T in generate_test_types([Float32, N0f8, Bool], [Gray]) + @test eltype(binarize(T, img, f)) == T + end + end + end + + @testset "Types" begin + # Gray + img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) + for (fname, f) in threshold_methods + type_list = generate_test_types([Float32, N0f8], [Gray]) + for T in type_list + img = T.(img_gray) + reffile = joinpath("References", fname*"_Gray.png") + @test_reference reffile Gray.(binarize(img, f)) by=binarization_equality() + end + end + + # Color3 + img_color = imresize(testimage("lena_color_256"); ratio=0.25) + for (fname, f) in threshold_methods + type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) + for T in type_list + img = T.(img_gray) + reffile = joinpath("References", fname*"_Color3.png") + @test_reference reffile Gray.(binarize(img, f)) by=binarization_equality() + end + end + end + + @testset "Numerical" begin + # Check that the image only has ones or zeros. + img = imresize(testimage("lena_gray_256"); ratio=0.25) + for (fname, f) in threshold_methods + img₀₁ = binarize(img, f) + non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) + @test length(non_zeros) == 0 + + # Check that ones and zeros have been assigned to the correct side of the threshold. + maxval, maxpos = findmax(Gray.(img)) + @test img₀₁[maxpos] == 1 + minval, minpos = findmin(Gray.(img)) + @test img₀₁[minpos] == 0 + end + end +end diff --git a/test/algorithms/unimodal.jl b/test/algorithms/unimodal.jl deleted file mode 100644 index 15d8080..0000000 --- a/test/algorithms/unimodal.jl +++ /dev/null @@ -1,76 +0,0 @@ -@testset "unimodal" begin - @info "Test: UnimodalRosin" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = UnimodalRosin() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # although Unimodal doesn't perform well on lena image, - # we need it to test the Color3 support. - - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = UnimodalRosin() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/UnimodalRosin_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = UnimodalRosin() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/UnimodalRosin_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = UnimodalRosin() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/algorithms/yen.jl b/test/algorithms/yen.jl deleted file mode 100644 index 82bcc56..0000000 --- a/test/algorithms/yen.jl +++ /dev/null @@ -1,73 +0,0 @@ -@testset "yen" begin - @info "Test: Yen" - - @testset "API" begin - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - img = copy(img_gray) - - # binarize - f = Yen() - binarized_img_1 = binarize(img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_1) == Gray{N0f8} - - binarized_img_2 = binarize(Gray{Bool}, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_2) == Gray{Bool} - - binarized_img_3 = similar(img, Bool) - binarize!(binarized_img_3, img, f) - @test img == img_gray # img unchanged - @test eltype(binarized_img_3) == Bool - - binarized_img_4 = copy(img_gray) - binarize!(binarized_img_4, f) - @test eltype(binarized_img_4) == Gray{N0f8} - - @test binarized_img_1 == binarized_img_2 - @test binarized_img_1 == binarized_img_3 - @test binarized_img_1 == binarized_img_4 - - for T in generate_test_types([Float32, N0f8, Bool], [Gray]) - @test eltype(binarize(T, img, f)) == T - end - end - - @testset "Types" begin - # Gray - img_gray = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Yen() - - type_list = generate_test_types([Float32, N0f8], [Gray]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Yen_Gray.png" Gray.(binarize(img, f)) by=binarization_equality() - end - - # Color3 - img_color = imresize(testimage("lena_color_256"); ratio=0.25) - f = Yen() - - type_list = generate_test_types([Float32, N0f8], [RGB, Lab]) - for T in type_list - img = T.(img_gray) - @test_reference "References/Yen_Color3.png" Gray.(binarize(img, f)) by=binarization_equality() - end - end - - @testset "Numerical" begin - # Check that the image only has ones or zeros. - img = imresize(testimage("lena_gray_256"); ratio=0.25) - f = Yen() - img₀₁ = binarize(img, f) - non_zeros = findall(x -> x != 0.0 && x != 1.0, img₀₁) - @test length(non_zeros) == 0 - - # Check that ones and zeros have been assigned to the correct side of the threshold. - maxval, maxpos = findmax(Gray.(img)) - @test img₀₁[maxpos] == 1 - minval, minpos = findmin(Gray.(img)) - @test img₀₁[minpos] == 0 - end - -end diff --git a/test/runtests.jl b/test/runtests.jl index 34e6aa0..5f48133 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,19 +9,11 @@ include("testutils.jl") @testset "ImageBinarization.jl" begin include("util.jl") + include("algorithms/single_histogram_threshold.jl") include("algorithms/adaptive_threshold.jl") - include("algorithms/balanced.jl") - include("algorithms/entropy.jl") - include("algorithms/intermodes.jl") - include("algorithms/minimum.jl") - include("algorithms/minimum_error.jl") - include("algorithms/moments.jl") include("algorithms/niblack.jl") - include("algorithms/otsu.jl") include("algorithms/polysegment.jl") include("algorithms/sauvola.jl") - include("algorithms/unimodal.jl") - include("algorithms/yen.jl") end nothing