|
| 1 | +using EarthMoversDistance: CSignature, CFlow, FLOW_ARRAY_SIZE |
| 2 | + |
| 3 | +struct EarthMoverBinned{F,H<:AbstractArray,C} <: ImageMetric |
| 4 | + binner!::F |
| 5 | + histA::H |
| 6 | + histB::H |
| 7 | + sigA::CSignature |
| 8 | + sigB::CSignature |
| 9 | + cost::C |
| 10 | + cflow::Vector{CFlow} |
| 11 | +end |
| 12 | + |
| 13 | +function costlb(ip, jp, edges::AbstractVector{T}) where T |
| 14 | + # lower-bound cost |
| 15 | + # Adjacent bins have no cost, because you may move by "epsilon" and cross over |
| 16 | + # to the next bin |
| 17 | + i, j = ip[], jp[] |
| 18 | + ii, jj = Int(i), Int(j) |
| 19 | + # The final bin is for NaN pixels, which match with zero cost |
| 20 | + (abs(ii-jj) <= 1 || ii == length(edges)+2 || jj == length(edges)+2) && return zero(Cfloat) |
| 21 | + return Cfloat(ii > jj ? edges[ii-1] - edges[jj] : edges[jj-1] - edges[ii]) |
| 22 | +end |
| 23 | + |
| 24 | +function EarthMoverBinned(edges::AbstractVector, cost=(i, j)->costlb(i, j, edges)) |
| 25 | + binner! = graybinner(edges) |
| 26 | + len = length(edges)+2 |
| 27 | + len > FLOW_ARRAY_SIZE && error("edges exceeded maximum allowed size of $(EarthMoversDistance.FLOW_ARRAY_SIZE)") |
| 28 | + histA, histB = Vector{Cfloat}(undef, len), Vector{Cfloat}(undef, len) |
| 29 | + sigA, sigB = CSignature(histA), CSignature(histB) |
| 30 | + costfun = @cfunction $cost Cfloat (Ref{Cfloat}, Ref{Cfloat}) |
| 31 | + cflow = fill(CFlow(0, 0, 0), FLOW_ARRAY_SIZE) |
| 32 | + metric = EarthMoverBinned(binner!, histA, histB, sigA, sigB, costfun, cflow) |
| 33 | + # For reasons that are not clear, the first usage errors unless we do the following: |
| 34 | + evaluate(metric, [0.0f0], [0.0f0]) |
| 35 | + return metric |
| 36 | +end |
| 37 | + |
| 38 | +function evaluate(em::EarthMoverBinned, imgA::AbstractArray, imgB::AbstractArray) |
| 39 | + em.binner!(em.histA, imgA) |
| 40 | + em.binner!(em.histB, imgB) |
| 41 | + # Do the ccall directly to avoid the allocations |
| 42 | + cflowsizeptr = Ref{Cint}(0) |
| 43 | + res = ccall((:emd, :emd), # function name and library name |
| 44 | + Cfloat, # return type |
| 45 | + (Ref{CSignature}, Ref{CSignature}, Ptr{Nothing}, Ref{CFlow}, Ref{Cint}), # argument types |
| 46 | + Ref(em.sigA), Ref(em.sigB), em.cost, em.cflow, cflowsizeptr) |
| 47 | + return res |
| 48 | +end |
| 49 | + |
| 50 | +function graybinner(edges::AbstractVector) |
| 51 | + @assert !Base.has_offset_axes(edges) |
| 52 | + ord = Base.Order.ForwardOrdering() |
| 53 | + @assert issorted(edges, ord) |
| 54 | + let ord=ord # julia issue #15276 |
| 55 | + function graybinner!(hist, A) |
| 56 | + fill!(hist, 0) |
| 57 | + for a in A |
| 58 | + if isnan(a) |
| 59 | + hist[end] += oneunit(eltype(hist)) |
| 60 | + else |
| 61 | + hist[searchsortedfirst(edges, a, ord)] += oneunit(eltype(hist)) |
| 62 | + end |
| 63 | + end |
| 64 | + return hist |
| 65 | + end |
| 66 | + end |
| 67 | +end |
0 commit comments