Closed johnnychen94 closed 5 years ago
@rjww You may be interested in these proposed changes too.
According to my understanding, window_size requires the information of the to-be-binarized image, which makes it not the intrinsic property of AdaptiveThreshold.
The AdaptiveThreshold
algorithm adapts the binarization threshold based on the intensity distribution of a sliding region-of-interest in the image. The size of the region-of-interest is determined by the window_size
. I reckon that in the context of thresholding, a window_size is something that is intrinsic to an adaptive method since the existence of a window is what often differentiates global thresholding from adaptive local thresholding. A sensible choice of window_size
does indeed depend on the size of the image. I would have put it as a property of AdaptiveThreshold
, but I would like to understand your point of view better since I may have misunderstood your reasoning.
rename: recommend_size --> recommended_size
We went with recommend_size
since we thought of it as an active process. You specify an image and it recommends the appropriate size. recommended_size
is what I would have given to the variable name, i.e. recommended_size = recommend_size(img)
.
a window_size is something that is intrinsic to an adaptive method since the existence of a window is what often differentiates global thresholding from adaptive local thresholding.
After a second thought, speaking of the concept, I think you're right that window_size
does differ the behavior of different AdaptiveThrehold
objects.
I'm thinking about not re-creating a new AdatptiveThrehold
object when we want to binarize a sequence of images, i.e., to avoid the following usage:
f1 = AdaptiveThreshold(window_size = recommend_size(img1))
img1_01 = binarize(img1, f)
f2 = AdaptiveThreshold(window_size = recommend_size(img2), percentage=15)
img2_02 = binarize(img2, f)
...
This won't be a performance issue for any non-trivial algorithms. But the usage isn't so concise to me.
Also, I think manually calling recommend_size
is tedious and trivial. The following line makes users no longer need to call it by themselves.
(f::AdaptiveThreshold)(out, img) = f(out, img, recommended_size(img))
and the usage becomes much easier:
f = AdaptiveThreshold()
img1_01 = binarize(img1, f)
img2_02 = binarize(img2, f)
f_30 = AdaptiveThreshold(percentage=30)
img3_03 = binarize(img3, f_30)
I guess this's a trade-off between conceptually-right and engineering-friendly, how do you think?
We went with recommend_size since we thought of it as an active process.
Good point. I was mainly writing python codes in last two weeks and there're functions like repeated
, sorted
that create a new object instead of doing an in-place operation repeat
and sort
. But it seems that Julia doesn't use this naming convention.
Another change I forgot to mention: to avoid unnecessary memory allocation, I choose to not creating an Array{Gray{Bool}, 2}
and return it. For example, all the following usage should be permitted and doesn't create a new array IMO:
binarize!(out::AbstractArray{Bool}, img::AbstractArray{<:Number}, f)::AbstractArray{Bool}
binarize!(out::AbstractArray{Bool}, img::AbstractArray{<:Colorant}, f)::AbstractArray{Bool}
binarize!(out::AbstractArray{Gray{Bool}}, img::AbstractArray{<:Number}, f)::AbstractArray{Gray{Bool}}
binarize!(out::AbstractArray{Gray{Bool}}, img::AbstractArray{<:Colorant}, f)::AbstractArray{Gray{Bool}}
I'm planning to do it in the coming commits.
Update:
This PR is ready to be reviewed.
Actually, n-D array isn't supported yet since boxdiff
is limited to 2D array.
@zygmuntszpak Regardless of the window_size
issue, can you have a look at the style of docstring and test case? If you're okay with it I can merge it and refactor other algorithms in the same way. Then we can create another PR for window_size
if you like it.
About docstring, I treat it as a cheat sheet explaining how to use it and what we can expect on its output, instead of the full explanation on its theoretical and coding details. Unfortunately, I find many of your docstrings are too long to read and understand even though they're written in good quality.
Merging #30 into api will increase coverage by
20.86%
. The diff coverage is93.33%
.
@@ Coverage Diff @@
## api #30 +/- ##
===========================================
+ Coverage 16.56% 37.42% +20.86%
===========================================
Files 19 20 +1
Lines 163 171 +8
===========================================
+ Hits 27 64 +37
+ Misses 136 107 -29
Impacted Files | Coverage Δ | |
---|---|---|
src/ImageBinarization.jl | 100% <ø> (ø) |
:arrow_up: |
src/adaptive_threshold.jl | 100% <100%> (+100%) |
:arrow_up: |
src/deprecations.jl | 66.66% <100%> (+66.66%) |
:arrow_up: |
src/compat.jl | 33.33% <33.33%> (ø) |
|
src/integral_image.jl | 75% <0%> (+6.25%) |
:arrow_up: |
src/BinarizationAPI/binarize.jl | 100% <0%> (+100%) |
:arrow_up: |
Continue to review full report at Codecov.
Legend - Click here to learn more
Δ = absolute <relative> (impact)
,ø = not affected
,? = missing data
Powered by Codecov. Last update ca471b8...8e1a2f4. Read the comment docs.
Also, I think manually calling
recommend_size
is tedious and trivial. The following line makes users no longer need to call it by themselves.
The recommend_size
function was only meant to be a convenience and a rule-of-thumb. There may be many instances where you don't want to use the recommended size, so we should still permit the user to specify a manual size. If I understood correctly your proposed solution will always use the recommend_size
function?
Perhaps one possible path is to let window_size
be a function
Base.@kwdef struct AdaptiveThreshold{T <: Function} <: AbstractImageBinarizationAlgorithm
percentage::Float64 = 15.0
window_size::T = recommend_size
end
and then have a constructor which takes a number and creates an anonymous function that returns the user-specified size.
AdaptiveThreshold(percentage::Number, window_size::Number) = AdaptiveThreshold(percentage, x-> window_size)
If I understood correctly your proposed solution will always use the recommend_size function?
No. The signature of binarize
for AdaptiveThreshold
is:
binarize(img, f [, window_size])
and usage examples are:
img = testimage("lena")
f = AdaptiveThreshold()
binarize(img, f) # infer window_size according to img
binarize(img, f, 16) # explicitly provide window_size
About docstring, I treat it as a cheat sheet explaining how to use it and what we can expect on its output, instead of the full explanation on its theoretical and coding details. Unfortunately, I find many of your docstrings are too long to read and understand even though they're written in good quality.
I have a different philosophy. I don't intend for the docstrings to be a cheat sheet, but rather an explanation of what the algorithm does, what assumptions it makes, what effect different options have etc. I'm not suggesting that the documentation that we have written attains all of those goals, but I would rather move towards that goal than away from it. I believe that the theoretical details are also useful if one wants to dig into the actual code and follow the implementation. I have kept the structure of the documentation headings in this package the same as in my other package: https://zygmuntszpak.github.io/ImageContrastAdjustment.jl/dev/ because I want to create a consistent look and feel.
The detailed explanation is meant to go in the section "Details" so that anyone that is not interested can just skip it. I understand that this causes a lot of scrolling if you consult the documentation from the REPL. I read the documentation predominantly in a browser, in Jupyter or in Atom, so I am prioritizing that user experience.
The headings "Options" are supposed to give you a "cheatsheet" of what you can fiddle with etc. I suppose that often the options can be written in more "bullet" point style. Certainly the ones in this package can be written in bullet points. There will be cases, however, where an explanation of an algorithm option may not fit as a single sentence in a bullet point. I had therefore opted to write everything as a sentence in order not to have to mix styles. We could use bullet points in this package instead, but I would still like to keep the top-level headings: Output, Details, Options etc in accordance with the other package. The inspiration for detailed documentation comes from Mathematica documentation, e.g.: https://reference.wolfram.com/language/ref/ImagePyramid.html
Update:
Deprecations:
window_size
is no longer a field of AdaptiveThreshold
, instead
it's a keyword argument in binarize
recommend_size
since it's automatically
called if not specified. So I'd like to unexport it in the future.Any further comment?
Its looking great, thank you very much Johnny!
As the very first PR in #29, I'm merging this now. The future PRs will be created parallelly using this as a template.
This PR co-operates #29 with some further noteworthy enhancement:
CartesianIndices
~ (The method is explained by https://julialang.org/blog/2016/02/iteration)recommend_size
, it should beround
instead ofdiv
/floor
TODO:
binarize(alg, img)
in favor ofbinarize(img, alg)
(deprecated in #29 )RFC:
recommend_size
-->recommended_size
window_size
as the property ofAdaptiveThreshold
:According to my understanding,
window_size
requires the information of the to-be-binarized image, which makes it not the intrinsic property ofAdaptiveThreshold
.So the proper usage IMO is:
We could add some one-liners to make the usage more convenient, i.e.,
by doing this the default
window_size
is automatically chosen according to the input image instead of hardcoded32
.Check
psnr
as an example, wherepeak_value
doesn't belong toPSNR
.