diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index eda471d..8f5af9c 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -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() @@ -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 \ No newline at end of file diff --git a/src/ImageMorphology.jl b/src/ImageMorphology.jl index 862362c..dffa6f8 100644 --- a/src/ImageMorphology.jl +++ b/src/ImageMorphology.jl @@ -24,6 +24,7 @@ include("connected.jl") include("clearborder.jl") include("extreme_filter.jl") include("extremum.jl") +include("fillholes.jl") include("ops/dilate.jl") include("ops/erode.jl") include("ops/closing.jl") @@ -129,8 +130,10 @@ export #feature_transform.jl feature_transform, - distance_transform, - clearborder, + distance_transform, clearborder, + + fillhole, + fillhole!, #leveling low_leveling, @@ -139,6 +142,7 @@ export high_leveling!, leveling, leveling!, + #extremum hmaxima, hmaxima!, diff --git a/src/fillholes.jl b/src/fillholes.jl new file mode 100644 index 0000000..75cc06f --- /dev/null +++ b/src/fillholes.jl @@ -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 \ No newline at end of file diff --git a/test/fillholes.jl b/test/fillholes.jl new file mode 100644 index 0000000..06b963f --- /dev/null +++ b/test/fillholes.jl @@ -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 + ] + + 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) + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index b74eaa1..6a70bc1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,6 +37,7 @@ include("testutils.jl") include("feature_transform.jl") include("leveling.jl") include("clearborder.jl") + include("fillholes.jl") @info "Beginning deprecation tests, warnings are expected" include("deprecations.jl") end