Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Usage:
# julia benchmark/run_benchmarks.jl
Usage:
julia benchmark/run_benchmarks.jl
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove the hashes?

using BenchmarkTools
using ImageCore
using ImageMorphology
Expand Down Expand Up @@ -91,8 +91,8 @@ end
SUITE["connected"] = BenchmarkGroup()
let grp = SUITE["connected"]
grp["label_components"] = @benchmarkable label_components($blobs)
grp["label_flatzones"] = @benchmarkable label_flatzones($cameraman, trues(3,3))
grp["label_lambdaflatzones"] = @benchmarkable label_lambdaflatzones($cameraman, trues(3,3),Gray{N0f8}(1.0/255.0))
grp["label_flatzones"] = @benchmarkable label_flatzones($cameraman, trues(3, 3))
grp["label_lambdaflatzones"] = @benchmarkable label_lambdaflatzones($cameraman, trues(3, 3), Gray{N0f8}(1.0 / 255.0))
end

SUITE["Maxtree"] = BenchmarkGroup()
Expand Down Expand Up @@ -136,3 +136,13 @@ let grp = SUITE["extremum"]
grp["regional_maxima"]["$sz×$sz"] = @benchmarkable regional_maxima($tst_img)
end
end

SUITE["clearborder"] = BenchmarkGroup()
let grp = SUITE["clearborder"]
grp["clearborder"] = @benchmarkable clearborder($blobs)
end

SUITE["fillhole"] = BenchmarkGroup()
let grp = SUITE["fillhole"]
grp["fillhole"] = @benchmarkable fillhole($blobs)
end
8 changes: 8 additions & 0 deletions src/ImageMorphology.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ include("connected.jl")
include("clearborder.jl")
include("extreme_filter.jl")
include("extremum.jl")
include("filholes.jl")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be fillholes.jl?

include("ops/dilate.jl")
include("ops/erode.jl")
include("ops/closing.jl")
Expand Down Expand Up @@ -130,15 +131,22 @@ export
#feature_transform.jl
feature_transform,
distance_transform,

#clearborder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments seem redundant

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#clearborder

clearborder,

#fillhole
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#fillhole

fillhole,
fillhole!,

#leveling
low_leveling,
low_leveling!,
high_leveling,
high_leveling!,
leveling,
leveling!,

#extremum
hmaxima,
hmaxima!,
Expand Down
58 changes: 58 additions & 0 deletions src/filholes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
fillhole(img; [dims])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the name of the file to match the base of the function, please

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the underscore prefix? Could we combine the function below and this one to a single signature?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prefix correspon to the most internal call, you will see this pattern in the whole project, something like "private" implem
But yes you're right in this special case its not relevant

end

function _fillhole!(out, img, se)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider typing the arguments here and extracting the number of dimensions and element type from the AbstractArray{T,N}.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree i could but why?
The function is generic, the speedup of binary cases could be to specialize mreconstruct for binary cases
Do you want to specialize this function at fillhole level ?

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if !all(x -> in(x, (1, 3)), strel_size(se))
if !all(in((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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we assuming one based indexing here?

If so, we may want to use Base.require_one_based_indexing

https://docs.julialang.org/en/v1/devdocs/offset-arrays/

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
64 changes: 64 additions & 0 deletions test/fillholes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@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
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
So as other image processing framework we don't fill hole touching the borders

]

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)
out = fillhole!(out, img)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the left side assignment here?

@test out == expected

# in place diamond
out = similar(img)
out = fillhole!(out, img, strel_diamond((3, 3)))
@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)
Copy link
Member

Choose a reason for hiding this comment

The 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
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ include("testutils.jl")
include("feature_transform.jl")
include("leveling.jl")
include("clearborder.jl")
include("fillholes.jl")
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down