-
Notifications
You must be signed in to change notification settings - Fork 20
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
[Morphology][Tasks] introduce generic fillholes algorithm #119
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
""" | ||
fillhole(img; [dims]) | ||
fillhole(img; se) | ||
|
||
Fill the holes in image 'img'. Could be binary or grascale | ||
|
||
The `dims` keyword is used to specify the dimension to process by constructing the box shape | ||
structuring element [`strel_box(img; dims)`](@ref strel_box). For generic structuring | ||
element, the half-size is expected to be either `0` or `1` along each dimension. | ||
|
||
The output has the same type as input image | ||
""" | ||
|
||
function fillhole(img; dims=coords_spatial(img)) | ||
return fillhole(img, strel_box(img, dims)) | ||
end | ||
|
||
function fillhole(img, se) | ||
return fillhole!(similar(img), img, se) | ||
end | ||
|
||
function fillhole!(out, img; dims=coords_spatial(img)) | ||
return fillhole!(out, img, strel_box(img, dims)) | ||
end | ||
|
||
function fillhole!(out, img, se) | ||
return _fillhole!(out, img, se) | ||
end | ||
|
||
function _fillhole!(out, img, se) | ||
N = ndims(img) | ||
|
||
axes(out) == axes(img) || throw(DimensionMismatch("images should have the same axes")) | ||
|
||
se_size = strel_size(se) | ||
if length(se_size) != N | ||
msg = "the input structuring element is not for $N dimensional array, instead it is for $(length(se_size)) dimensional array" | ||
throw(DimensionMismatch(msg)) | ||
end | ||
if !all(x -> in(x, (1, 3)), strel_size(se)) | ||
msg = "structuring element with half-size larger than 1 is invalid" | ||
throw(DimensionMismatch(msg)) | ||
end | ||
|
||
tmp = similar(img) | ||
|
||
# fill marker image with max | ||
fill!(tmp, typemax(eltype(img))) | ||
# fill borders with 0 | ||
dimensions = size(tmp) | ||
outerrange = CartesianIndices(map(i -> 1:i, dimensions)) | ||
innerrange = CartesianIndices(map(i -> (1 + 1):(i - 1), dimensions)) | ||
for i in EdgeIterator(outerrange, innerrange) | ||
tmp[i] = 0 | ||
end | ||
|
||
return mreconstruct!(erode, out, tmp, img, se) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
@testset "fillhole" begin | ||
#binary | ||
img = Bool[ | ||
0 0 0 0 0 0 0 | ||
0 1 1 1 1 1 0 | ||
0 1 0 0 0 1 0 | ||
0 1 0 0 0 1 0 | ||
0 1 0 0 0 1 0 | ||
0 1 1 1 1 1 0 | ||
0 0 0 0 0 0 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a test where the 0s at the border are not a single connected component? Also the case where there are no zeros on the border m. Also add a test for multiple interior holes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that we could say nothing for "holes" in the border, anyway we don't have acess to underlyting image domain, are these really holes |
||
] | ||
|
||
expected = Bool[ | ||
0 0 0 0 0 0 0 | ||
0 1 1 1 1 1 0 | ||
0 1 1 1 1 1 0 | ||
0 1 1 1 1 1 0 | ||
0 1 1 1 1 1 0 | ||
0 1 1 1 1 1 0 | ||
0 0 0 0 0 0 0 | ||
] | ||
|
||
out = fillhole(img) | ||
@test eltype(out) == Bool | ||
@test out == expected | ||
|
||
# in place | ||
out = similar(img) | ||
fillhole!(out, img) | ||
@test out == expected | ||
|
||
# in place diamond | ||
out = similar(img) | ||
fillhole!(out, img, strel_diamond((3, 3))) | ||
@test out == expected | ||
|
||
# more holes | ||
#binary | ||
img = Bool[ | ||
0 0 0 0 0 1 1 0 | ||
0 1 1 1 0 0 0 0 | ||
0 1 0 1 0 0 0 0 | ||
0 1 1 1 0 0 0 0 | ||
0 0 0 1 1 1 0 0 | ||
1 0 0 1 0 1 0 0 | ||
1 0 0 1 1 1 0 0 | ||
1 0 0 0 0 0 0 0 | ||
] | ||
|
||
expected = Bool[ | ||
0 0 0 0 0 1 1 0 | ||
0 1 1 1 0 0 0 0 | ||
0 1 1 1 0 0 0 0 | ||
0 1 1 1 0 0 0 0 | ||
0 0 0 1 1 1 0 0 | ||
1 0 0 1 1 1 0 0 | ||
1 0 0 1 1 1 0 0 | ||
1 0 0 0 0 0 0 0 | ||
] | ||
|
||
out = fillhole(img) | ||
@test eltype(out) == Bool | ||
@test out == expected | ||
|
||
# in place | ||
out = similar(img) | ||
fillhole!(out, img) | ||
@test out == expected | ||
|
||
|
||
# "holes" touching the borders | ||
# by definitions we can't say anything in this case | ||
# because we have no acess to the underlying image domain | ||
# so like other framework, leave these holes not filled | ||
|
||
#binary | ||
img = Bool[ | ||
0 0 0 0 0 0 0 0 | ||
0 0 0 0 0 0 1 1 | ||
0 0 0 0 0 0 1 0 | ||
0 0 0 0 0 0 1 0 | ||
1 1 1 1 0 0 1 1 | ||
1 0 0 1 0 0 0 0 | ||
1 1 1 1 0 0 0 0 | ||
1 0 0 0 0 0 0 0 | ||
] | ||
|
||
expected = Bool[ | ||
0 0 0 0 0 0 0 0 | ||
0 0 0 0 0 0 1 1 | ||
0 0 0 0 0 0 1 0 | ||
0 0 0 0 0 0 1 0 | ||
1 1 1 1 0 0 1 1 | ||
1 1 1 1 0 0 0 0 | ||
1 1 1 1 0 0 0 0 | ||
1 0 0 0 0 0 0 0 | ||
] | ||
|
||
out = fillhole(img) | ||
@test eltype(out) == Bool | ||
@test out == expected | ||
|
||
# in place | ||
out = similar(img) | ||
fillhole!(out, img) | ||
@test out == expected | ||
|
||
#gray | ||
img = [ | ||
3 3 3 3 3 3 3 3 3 3 | ||
3 4 4 4 3 3 4 4 4 3 | ||
3 4 1 4 3 3 4 1 4 3 | ||
3 4 4 4 3 3 4 4 4 3 | ||
3 3 3 3 3 3 3 3 3 3 | ||
] | ||
|
||
expected = [ | ||
3 3 3 3 3 3 3 3 3 3 | ||
3 4 4 4 3 3 4 4 4 3 | ||
3 4 4 4 3 3 4 4 4 3 | ||
3 4 4 4 3 3 4 4 4 3 | ||
3 3 3 3 3 3 3 3 3 3 | ||
] | ||
|
||
out = fillhole(img) | ||
@test out == expected | ||
|
||
msg = "the input structuring element is not for 1 dimensional array, instead it is for 2 dimensional array" | ||
@test_throws DimensionMismatch(msg) fillhole(rand(10), strel_box((3, 3))) | ||
|
||
se = strel_diamond((7, 7)) | ||
msg = "structuring element with half-size larger than 1 is invalid" | ||
@test_throws DimensionMismatch(msg) fillhole(rand(10, 10), se) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test for an OffsetArray. Either check for the appropriate exception or correct function. |
||
|
||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ include("testutils.jl") | |
include("feature_transform.jl") | ||
include("leveling.jl") | ||
include("clearborder.jl") | ||
include("fillholes.jl") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this indented? |
||
@info "Beginning deprecation tests, warnings are expected" | ||
include("deprecations.jl") | ||
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.
For generic programming:
tmp[i] = zero(T)
where I assumeT = eltype(img)
The difference is very small but noteworthy:
tmp[i] = 0
would be valid if there's a conversion from0
(Int) toeltype(tmp)
tmp[i] = zero(T)
would be valid as long aszero(T)
is defined (which is true for almost all number-like types)Why we should prefer the
zero(T)
version is that: not all types (should) support (implicit) conversion fromInt
. The following is an JuliaImages example, but you can quickly come up with many like it: