Skip to content
4 changes: 3 additions & 1 deletion src/ImageFiltering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using ColorVectorSpace # for filtering RGB arrays
using Compat
using Base: Indices, tail, fill_to_length, @pure, depwarn

export Kernel, KernelFactors, Pad, Fill, Inner, NA, NoPad, Algorithm, imfilter, imfilter!, mapwindow, imgradients, padarray, centered, kernelfactors, reflect
export Kernel, KernelFactors, Pad, Fill, Inner, NA, NoPad, Algorithm, imfilter, imfilter!, median_direct!, median_histogram!, mapwindow, imgradients, padarray, centered, kernelfactors, reflect

@compat FixedColorant{T<:Normed} = Colorant{T}
@compat StaticOffsetArray{T,N,A<:StaticArray} = OffsetArray{T,N,A}
Expand Down Expand Up @@ -68,8 +68,10 @@ include("imfilter.jl")
include("specialty.jl")

include("mapwindow.jl")

using .MapWindow


function __init__()
# See ComputationalResources README for explanation
push!(LOAD_PATH, dirname(@__FILE__))
Expand Down
205 changes: 200 additions & 5 deletions src/mapwindow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ module MapWindow
using DataStructures, TiledIteration
using ..ImageFiltering: BorderSpecAny, Pad, Fill, borderinstance, _interior, padindex, imfilter
using Base: Indices, tail
using FixedPointNumbers,Colors
using ImageCore:normedview

export mapwindow
export median_direct!
export median_histogram!

"""
mapwindow(f, img, window, [border="replicate"]) -> imgf
Expand Down Expand Up @@ -41,6 +45,83 @@ and then `mapwindow(f, img, (m,n))` should filter at the 75th quantile.

See also: [`imfilter`](@ref).
"""


function median_direct!(v::AbstractVector)
inds = indices(v,1)
Base.middle(Base.select!(v, (first(inds)+last(inds))÷2, Base.Order.ForwardOrdering()))
end

# This is a implementation of Fast 2D median filter extended for nD
# http://ieeexplore.ieee.org/document/1163188/

function median_histogram!(v::AbstractVector{UInt8},m_histogram,mode,window)

# Handle the boundary points with mode -1
if(mode==-1)
inds = indices(v,1)
return convert(eltype(v),Base.middle(Base.select!(v, (first(inds)+last(inds))÷2, Base.Order.ForwardOrdering())))
else
window_size=size(v,1)
dims = map(x->last(x)-first(x)+1,window)
width = last(window[1])-first(window[1])+1
inds = indices(v,1)
if mode == 0
# Update the histogram according to new entries
for i = first(inds):last(inds)
id = v[i]+1;
m_histogram[id]+= 1
end
# Compute the median
tempsum = 0
m_index = -1
for i in linearindices(m_histogram)
tempsum+= m_histogram[i]
if tempsum>=trunc(Int64,window_size/2)+1
m_index=i-1
break
end
end
# Clear the histogram from previous value
for i = first(inds):width:dims[1]*dims[2]
for j = i: dims[1]*dims[2]:last(inds)
id = v[j]+1
m_histogram[id]-= 1
end
end
return convert(UInt8,m_index)
elseif mode == 1
# Update the histogram according to new entries
for i = width:width:dims[1]*dims[2]
for j= i: dims[1]*dims[2]:last(inds)
id = v[j]+1
m_histogram[id]+= 1
end
end
# Compute the median
tempsum = 0
m_index=-1
for i in linearindices(m_histogram)
tempsum+= m_histogram[i]
if tempsum>= trunc(Int64,window_size/2)+1
m_index = i-1
break
end
end
# Clear the histogram from previous value
for i = first(inds):width:dims[1]*dims[2]
for j = i: dims[1]*dims[2]:last(inds)
id = v[j]+1
m_histogram[id]-= 1
end
end
return convert(UInt8,m_index)
end
end

end


function mapwindow(f, img::AbstractArray, window::Dims, args...; kwargs...)
all(isodd(w) for w in window) || error("entries in window must be odd, got $window")
halfsize = map(w->w>>1, window)
Expand Down Expand Up @@ -68,13 +149,109 @@ end

mapwindow(f, img, window::AbstractArray, args...; kwargs...) = mapwindow(f, img, (window...,), args...; kwargs...)



# This dispatch takes input of type Union{AbstractArray{N0f8,N},AbstractArray{ColorTypes.Gray{N0f8},N}} and decides whether to use median_histogram or median_direct method

function mapwindow{N}(f::Union{typeof(median!),typeof(median_histogram!)},
img::Union{AbstractArray{N0f8,N},AbstractArray{ColorTypes.Gray{N0f8},N}},
window::Indices{N},
border::BorderSpecAny,
shape=default_shape(f);
callmode=:copy!)
f = replace_function(f,window)
img_n=map(x->gray(x),img)
img_n=reinterpret.(img_n)
# This ensures different method of image traversal for median_direct
if(typeof(f) == typeof(median_direct!))
out = _mapwindow(median_direct!, img_n, window, border, default_shape(f); callmode=callmode)
out = normedview(map(x->convert(UInt8,x),out))
return map(x->convert(eltype(img),x),out)
end
inds = indices(img_n)
inner = _interior(inds, window)
if callmode == :copy!
buf = Array{UInt8}(map(length, window))
bufrs = shape(buf)
Rbuf = CartesianRange(size(buf))
offset = CartesianIndex(map(w->first(w)-1, window))
# To allocate the output, we have to evaluate f once
Rinner = CartesianRange(inner)
# Initialise the mode to zero and histogram consisting of 255 bins to zeros
mode = 0
m_histogram=zeros(Int64,(256,))
if !isempty(Rinner)
out = similar(img_n, typeof(f(bufrs,m_histogram,mode,window)))
Rwin = CartesianRange(map(+, window, first(Rinner).I))
copy!(buf, Rbuf, img_n, Rwin)
prev_mode = 0
prev_col = 1
for I in Rinner
curr_col=I.I[2]
if(column_change(curr_col,prev_col))
m_histogram=zeros(Int64,(256,))
prev_mode=0
end
prev_col = curr_col
Rwin = CartesianRange(map(+, window, I.I))
copy!(buf, Rbuf, img_n, Rwin)
# Mode 0 corresponds to refilling the empty histogram with all the points in the window
if prev_mode == 0
Rwin = CartesianRange(map(+, window, I.I))
copy!(buf, Rbuf, img_n, Rwin)
out[I] = f(bufrs,m_histogram,0,window)
prev_mode=1
continue
# Mode 1 corresponds to adding only the new points to the histogram and removing the old ones
elseif prev_mode == 1
Rwin = CartesianRange(map(+, window, I.I))
copy!(buf, Rbuf, img_n, Rwin)
out[I] = f(bufrs,m_histogram,1,window)
prev_mode=1
continue
end

end

else
copy_win!(buf, img_n, first(CartesianRange(inds)), border, offset)
out = similar(img_n, typeof(f(bufrs,m_histogram,mode,window)))
end
# Now pick up the edge points we skipped over above
for I in EdgeIterator(inds, inner)
# Handle the edge points with mode -1 [finding median using simple sort]
mode =-1
copy_win!(buf, img_n, I, border, offset)
out[I] = f(bufrs,m_histogram,mode,window)
end

else
# TODO: implement :view
error("callmode $callmode not supported")
end
out = normedview(out)
out = map(x->convert(eltype(img),x),out)
out
end

function mapwindow{T,N}(f::typeof(median!),
img::AbstractArray{T,N},
window::Indices{N},
border::BorderSpecAny;
callmode=:copy!)
_mapwindow(median_direct!, img, window, border, default_shape(f); callmode=callmode)

end

function mapwindow{T,N}(f,
img::AbstractArray{T,N},
window::Indices{N},
border::BorderSpecAny;
callmode=:copy!)
_mapwindow(replace_function(f), img, window, border, default_shape(f); callmode=callmode)

end

function _mapwindow{T,N}(f,
img::AbstractArray{T,N},
window::Indices{N},
Expand Down Expand Up @@ -116,6 +293,14 @@ function _mapwindow{T,N}(f,
out
end



function column_change(curr_col,prev_col)
return (curr_col - prev_col!=0)
end



# For copying along the edge of the image
function copy_win!{T,N}(buf::AbstractArray{T,N}, img, I, border::Pad, offset)
win_inds = map(+, indices(buf), (I+offset).I)
Expand Down Expand Up @@ -143,6 +328,8 @@ function copy_win!{T,N}(buf::AbstractArray{T,N}, img, I, border::Fill, offset)
buf
end



### Optimizations for particular window-functions

mapwindow(::typeof(extrema), A::AbstractArray, window::Dims) = extrema_filter(A, window)
Expand Down Expand Up @@ -273,13 +460,21 @@ end
# This is slightly faster than a circular buffer
@inline cyclecache(b, x) = b[1], (Base.tail(b)..., x)


replace_function(f,window) = replace_function(f)
replace_function(f) = f
replace_function(::typeof(median!)) = function(v)
inds = indices(v,1)
Base.middle(Base.select!(v, (first(inds)+last(inds))÷2, Base.Order.ForwardOrdering()))

function replace_function(::typeof(median!), window)
# The threshold is defined for changing the method of finding median [Using sorting or Using Histogram]
if prod(map(length, window)) <= 81
return median_direct!
end
median_histogram!
end


default_shape(::Any) = identity
default_shape(::typeof(median!)) = vec
default_shape(::Union{typeof(median!),typeof(median_direct!),typeof(median_histogram!)}) = vec

end

end