-
Notifications
You must be signed in to change notification settings - Fork 21
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
foundamental support for structuring element concept #65
Conversation
A second thought: the SEAlias seems a bad design because it seems quite inconvenient to use in practice. A better idea is perhaps introducing two new types:
These two are special cases of the above |
5444d41
to
87e6383
Compare
This seems to be quite convenient to use so I think it's stable. In a summary, this PR introduces a few helper functions and types to properly support the structuring element concept:
To support multiple dispatches on special SE cases, two generators and corresponding types are provided:
To develop generic morphology operation without lossing the opportunities to provide optimized version, we can use the following dispatching pattern: f(img, Ω=strel_diamond(img)) = _f(strel_type(Ω), img, Ω)
f(img, dims::Dims) = f(img, strel_diamond(img, dims))
function _f(::MorphologySE, img, Ω)
... # generic implementation
end
function _f(::SEDiamond, img, Ω)
... # optimized implementation for diamond shape SE
end
function _f(::SEDiamond, img::AbstractArray{Bool}, Ω)
... # optimized implementation for diamond shape SE and Bool array
end Before merging this PR, I'll first verify this design by solving #11 and upgrading #60 to see if this works well. |
|
||
@testset "center point" begin | ||
for se in [Bool[1 0 0; 0 1 0; 0 0 1], Bool[1 0 0; 0 0 0; 0 0 1]] | ||
# center point is always excluded in offsets |
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.
Why? the list of offset could contains the center, sometimes we use it sometimes not
Awesome :) my level of julia is not at the level to properly review this PR-> I will reads some docs |
src/structuring_element.jl
Outdated
|
||
The N-dimensional structuring element in terms of displacement offset. | ||
""" | ||
struct SEOffsets{N} <: MorphologySE{N} end |
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.
the center is just a displacement of (0,....)
0 0 1 0 0 | ||
0 0 0 0 0 | ||
|
||
julia> strel_size(se) # is not (5, 5) |
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.
great, we could speed up some process by distinguish border point than non border point along iteration
src/structuring_element.jl
Outdated
0 0 0 0 0 | ||
|
||
julia> se = strel_diamond((5, 5), (1, )) | ||
5×5 ImageMorphology.SEDiamondArray{2, 1, 1}: |
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 surprise me but ok
I feel the center pixel is usually handled separately, for instance, to get the local maxima using the offset representation, it is: function _maxima(A, p::CartesianIndex, offsets)
out = A[p] # center pixel is retrived separately.
for o in offsets
out = max(out, A[p+o])
end
return out
end If we include the center pixel by default, then you'll have to 1) either do one more Maybe I should add a keyword |
Ok i just thinking of algorithm which need the center INSIDE the Neighbordlist maybee some skeleton one |
87e6383
to
e8a4cb0
Compare
Changes summary:
Going to merge this in a few days if without further comments. This is a long PR, but most of the contents are documentation and test. |
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.
@andrewjradcliffe Thank you for improving the docs!
This commit tries to bring a unified framework for the structuring element (strel) concept. Two commonly used representations are supported: - SEMask: the connectivity mask as `AbstractArray{Bool}` - SEOffset: the displacement offsets as `AbstractArray{CartesianIndex}` This commit also introduces two special SE constructors `strel_diamond` and `strel_window`. Concrete algorithms can provide optimized implementations on them without compromising the performance.
Typically, a fast window filter implementation separates the loop into two parts: 1) inner loop where bounds check are unnecessary, and 2) boundary loop where one has to carefully deal with boundary conditions. This helper function can be used to infer the minimal radius of the strucuring element so that separating the loop becomes an easy work with the help of TiledIteration.
1c54eb1
to
29cd548
Compare
Rebased. Plan to do a merge commit without squash later today. |
29cd548
to
30fd8ca
Compare
I disliked this name at first glance because I wasn't aware of the concept of "structuring element" but when I started to play around with it to generalize To quickly introduce new users to this concept and make them familiar with |
30fd8ca
to
a72e625
Compare
As a proof of concept, #72 introduces |
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 like it but I think there are a few conceptual issues we should consider:
- how to handle anisotropic imaging where individual dimensions have different scaling? This is common in biomedical 3d imaging, where the 3rd dimension often has reduced resolution. The natural metric on such spaces is something like
Δx^2/sx^2 + Δy^2/sy^2 +Δz^2/sz^2
but this does not fit well with notions of "radius" which are used heavily here unless you also specify(sx, sy, sz)
. - from a conceptual standpoint,
AbstractArray{Bool}
elements should probably have their axes centered on zero. This is also consistent with howimfilter
works, and of course many morphological operations are equivalent to filtering (e.g.,dilate
is justmapwindow(maximum, kernel)
. - we should think about color images. https://www.hindawi.com/journals/mpe/2012/678326/ has a nice overview. I favor the "RO" approach, where the user can pass in an
f
mapping colors to real values and then ordering operations are performed on this real value.
|
||
# Structuring element | ||
|
||
Structuring Element (SE) is the key concept in morphology to indicate the connectivity and the |
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.
Structuring element is definitely a name we want to introduce (it's very widespread), but in many operations they are simply a binary kernel (e.g., as used by imfilter
). I think that's worth pointing out.
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'll add another example to illustrate the difference between extreme_filter
and mapwindow
in #72, if that's what you're expecting.
src/structuring_element.jl
Outdated
""" | ||
SEDiamond{N}([size], [dims]; r=1) | ||
|
||
The N-dimensional diamond shape structuring element. |
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.
What is dims
for? Is it a list of dimensions over which this operates? How does that differ from having size[d] = 0
? Why is r=1
the default rather than r[d] = size[d] ÷ 2
? For anisotropic imaging (commonplace in 3d biomedical imaging), presumably you might want to look several pixels in the high-resolution axes but only at nearest-neighbors in the low-resolution axes.
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.
For the r::Int
decision, this is mainly because I'm lazy... because if we extend r
to be a tuple, implementing an efficient algorithm to check if a given point is within the N-dimensional diamond(polytope) isn't something trivial.
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.
Separating out dims
allows me to generalize the definition a bit to skip certain dimensions as extremefilt!
allows, yet without the need to play with the is-point-in-polytope algorithm.
src/structuring_element.jl
Outdated
1 1 1 | ||
1 1 1 | ||
|
||
julia> se = strel_window((3,3), (1,)) # 3×3 mask along dimension 1 |
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.
Personally I'd rather think of this as
1
1
1
and specify it as strel_window((3,1))
. Or in terms of half-sizes, strel_window((1,0))
.
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 would think both are equivalent representations so I won't be too worried about it. It totally depends on how the user creates it.
Actually, if we play with the strel
conversion, we'll see they're eventually unified to the same:
julia> strel(Bool, strel(CartesianIndex, strel_box((3,1))))
3×1 BitMatrix:
1
1
1
julia> strel(Bool, strel(CartesianIndex, strel_box((3,3), (1,))))
3×1 BitMatrix:
1
1
1
But I'll make the default strel_box(img, dims)
to use the smaller version:
function strel_box(img::AbstractArray{T,N}, dims=coords_spatial(img); kw...) where {T,N}
- return strel_box(ntuple(i->3, N), dims; kw...)
+ return strel_box(ntuple(i->in(i, dims) ? 3 : 1, N), dims; kw...)
end
a72e625
to
5d78a88
Compare
A few changes are involved in this commit: - `SEBox` and `SEDiamond` now stores the axes instead of the size because we expect the mask to be zero-centered. - `strel_box` drops the useless `dims` field; a generalization. - the documentation is revisited with more examples and explainations.
Now it becomes more of a burden to maintain Julia < 1.6 compatibility, thus I figure it is time to retire 1.3 support.
5d78a88
to
a8cc3e3
Compare
The new commits address some of the comments from @timholy
To relieve me from the maintenance burden, I decided to drop support for Julia < 1.6.
The current We might want to introduce a way to cartesian product two low-dimensional SEs into a high dimensional one just like
Yes, but it's orthogonal to the structuring element as far as I understand. The color reduced-order provides a "meaningful" way to define the This becomes a long PR. Since this design works quite well on #72, I'm going to merge this. If there are more comments, I'll try to address them in PRs. |
Thanks for adding what is sure to become well used functionality! |
@johnnychen94 pretty good job :) |
@ThomasRetornaz Yes! I think this becomes quite convenient to use based on my
I've already made a few optimizations for the diamond shape and for the Boolean array case. Not sure if it's the best version, though. If you want to take it a try, it would be fantastic!
I noticed that this library uses GPL licenses. Thus if you port the code here, you'll have to clearly keep the license. And there will need to be some extra work if we want to make ImageMorphology a pure MIT license. |
Yes maybee a tag could be useful |
@ThomasRetornaz I just asked this in slack, and unfortunately, this part of code, if ported, should still be GPL code unless you have a clean implementation according to the papers. As far as I've concerned, it's okay to have a portion of the codebase being GPL licensed, but here we just need to be clear about the license to avoid unnecessary issues in the future.
|
@johnnychen94 thanks i will implement algorithms with this in mind |
foundamental support for structuring element concept
This PR tries to unify the structuring element concept via
strel
function and a few internal trait types. For the proof of concept, will work on #11 and #63 on top of this.This function converts from one structuring element representation to
the other. Currently, three structuring element representations are
supported:
- SEAliasMorphologySE
and the above subtypes are internal types. For userinterface, they are:
strel(CartesianIndex, se)
strel(Bool, se)
- Alias:strel([T], symbol, N)
cc: @ThomasRetornaz