-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor and enhance AdaptiveThreshold method #30
refactor and enhance AdaptiveThreshold method #30
Conversation
@rjww You may be interested in these proposed changes too. |
The
We went with |
After a second thought, speaking of the concept, I think you're right that I'm thinking about not re-creating a new 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 (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? |
Good point. I was mainly writing python codes in last two weeks and there're functions like Another change I forgot to mention: to avoid unnecessary memory allocation, I choose to not creating an 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. |
Changes: * fixes a keywords constructor bug introduced by incorrect order it should be `AdaptiveThreshold(percentage, window_size)` instead of `AdaptiveThreshold(window_size, percentage)` * support n-D images with the usage of CartesianIndices * generalize the type annotation of percentage from `Int` to `Float64` * move argument validation of AdaptiveThreshold to its inner constructor * correct the result of recommend_size from `floor` to `round` -- the closest integer
Update: This PR is ready to be reviewed. Actually, n-D array isn't supported yet since @zygmuntszpak Regardless of the 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. |
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
The Perhaps one possible path is to let 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) |
No. The signature of 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 |
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. 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 |
Changes: * roll back to the previous docstring style * `window_size` is no longer a field of `AdaptiveThreshold`, instead it's a keyword argument in `binarize` * there's no need to export `recommend_size` since it's automatically called if not specified
Update:
Deprecations:
Any further comment? |
@@ -11,6 +11,7 @@ HistogramThresholding = "2c695a8d-9458-5d45-9878-1b8a99cf7853" | |||
ImageContrastAdjustment = "f332f351-ec65-5f6a-b3d1-319c6670881a" | |||
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" | |||
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" | |||
MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we use this package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
of_eltype(Gray, img)
performs almost the same as Gray.(img)
except that it doesn't allocate additional memory.
julia> @benchmark of_eltype(Gray, img)
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 29.858 ns (0.00% GC)
median time: 31.279 ns (0.00% GC)
mean time: 37.766 ns (14.93% GC)
maximum time: 40.667 μs (99.91% GC)
--------------
samples: 10000
evals/sample: 994
julia> @benchmark Gray.(img)
BenchmarkTools.Trial:
memory estimate: 508.19 KiB
allocs estimate: 4
--------------
minimum time: 31.353 μs (0.00% GC)
median time: 38.761 μs (0.00% GC)
mean time: 50.567 μs (22.40% GC)
maximum time: 42.538 ms (99.85% GC)
--------------
samples: 10000
evals/sample: 1
type_list = generate_test_types([Float32, N0f8], [Gray]) | ||
for T in type_list | ||
img = T.(img_gray) | ||
@test_reference "References/AdaptiveThreshold_Gray.png" Gray.(binarize(img, f)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know there was such a thing as @test_reference. Its a great macro! However, I can't see where you added using ReferenceTests
and if you have added it the package the Project.toml ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a PR to the api
branch, and I've updated some codes there so it's not shown in diff. ReferenceTests
is one of them. Spliting them apart make it easier to review.
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. |
* move implementations to AdaptiveThreshold functor * Rewrite AdaptiveThreshold with CartesianIndices to support n-D images * update and simplify the docstring * enhance the test codes and fix several bugs * add CartetianIndex compat to Julia 1.0 * deprecate `window_size` and `recommend_size`
Changes: * refactor the codebase using the functor API discussed in #26 * enhance the API by introducing a submodule `BinarizationAPI` * add in-place function `binarize!` * support Color3 inputs * add more test codes * slightly enhance the documentation Breaking changes (Deprecated in 0.3): * swap the argument order discussed in #23 ( d1f8309) * unexport `recommend_size` in favor of #41, i.e., `AdaptiveThreshold(img)` instead of `recommend_size(img)` ( PR: #30 #45) * made `window_size` of `AdaptiveThreshold` not an optional argument. ( PR: #45 )
This PR co-operates #29 with some further noteworthy enhancement:
[Enhancement]: support n-D image by taking advantages of(The method is explained by https://julialang.org/blog/2016/02/iteration)CartesianIndices
recommend_size
, it should beround
instead ofdiv
/floor
TODO:
binarize(alg, img)
in favor ofbinarize(img, alg)
(deprecated in [WIP] refactor codebase with functor APIs #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
.