Skip to content
Snippets Groups Projects
traceskeleton.jl 7.61 KiB
Newer Older
  • Learn to ignore specific revisions
  • using FileIO
    using Printf
    
    # from https://github.com/LingDong-/skeleton-tracing
    
    const HORIZONTAL = 1
    const VERTICAL   = 2
    
    function thinning_zs_iteration(bim,iter)
        h,w = size(bim)
        marker = zeros(Bool,h,w)
        for i=2:h-1
            for j=2:w-1
                p2 = bim[i-1,j]   & 1
                p3 = bim[i-1,j+1] & 1
                p4 = bim[i,j+1]   & 1
                p5 = bim[i+1,j+1] & 1
                p6 = bim[i+1,j]   & 1
                p7 = bim[i+1,j-1] & 1
                p8 = bim[i,j-1]   & 1
                p9 = bim[i-1,j-1] & 1
                a = (p2 == 0 && p3 == 1)+ (p3 == 0 && p4 == 1)+
                    (p4 == 0 && p5 == 1)+ (p5 == 0 && p6 == 1)+
                    (p6 == 0 && p7 == 1)+ (p7 == 0 && p8 == 1)+
                    (p8 == 0 && p9 == 1)+ (p9 == 0 && p2 == 1)
                b = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9
                m1 = iter==0 ? (p2*p4*p6) : (p2*p4*p8);
                m2 = iter==0 ? (p4*p6*p8) : (p2*p6*p8);
                if a == 1 && (b >= 2 && b <= 6) && m1 == 0 && m2 == 0
                    marker[i,j] = true;
                end
            end
        end
        nim = bim .& (.!marker)
        return nim
    end
    
    function thinning_zs(bim)
        while true
            oim = copy(bim)
            bim = thinning_zs_iteration(bim,0)
            bim = thinning_zs_iteration(bim,1)
            dif = sum(bim . oim)
            if dif == 0
                break
            end
        end
        return bim
    end
    
    function merge_impl!(c0,c1,i,sx,isv,mode)
        b0 = Bool(mode >> 1 & 1)
        b1 = Bool(mode >> 0 & 1)
        mj = -1
        md = 4
        p1 = c1[i][b1 ? 1 : end]
    
        if abs(p1[isv ? 2 : 1] - sx) > 0
            return false
        end
        # find the best match
        for j=1:length(c0)
            p0 = c0[j][b0 ? 1 : end]
            if abs(p0[isv ? 2 : 1] - sx) > 1
                continue
            end
            d = abs(p0[isv ? 1 : 2] - p1[isv ? 1 : 2])
            if d < md
                mj = j
                md = d
            end
        end
        if mj != -1
            j = mj
            if b0 && b1
                splice!(c0[j],1:0,reverse(c1[i]))
            elseif !b0 && b1
                append!(c0[j],c1[i])
            elseif b0 && !b1
                splice!(c0[j],1:0,c1[i])
            else
                append!(c0[j],reverse(c1[i]))
            end
            deleteat!(c1,i)
            return true
        end
        return false
    end
    
    function translate!(c,sx,dr)
        x,y = sx-1,0
        if dr == VERTICAL
            y,x = sx-1,0
        end
        for i=1:length(c)
            for j=1:length(c[i])
                c[i][j][1]+=x
                c[i][j][2]+=y
            end
        end
    end
    
    function merge_frags!(c0,c1,sx,dr)
        translate!(c1,sx,dr)
        if length(c0) == 0
            append!(c0,c1)
            return
        end
        if length(c1) == 0
            return
        end
        l = length(c1)
        for i=l:-1:1
            if dr == HORIZONTAL
                if merge_impl!(c0,c1,i,sx,false,1) continue end
                if merge_impl!(c0,c1,i,sx,false,3) continue end
                if merge_impl!(c0,c1,i,sx,false,0) continue end
                if merge_impl!(c0,c1,i,sx,false,2) continue end
            elseif dr == VERTICAL
                if merge_impl!(c0,c1,i,sx,true ,1) continue end
                if merge_impl!(c0,c1,i,sx,true ,3) continue end
                if merge_impl!(c0,c1,i,sx,true ,0) continue end
                if merge_impl!(c0,c1,i,sx,true ,2) continue end
            end         
        end
        append!(c0,c1)
        return
    end
    
    function chunk_to_frags(bim)
        h,w = size(bim)
        frags = []
        on = false
        li = -1
        lj = -1
        for k=1:h+h+w+w-4
            i = 0
            j = 0
            if k <= w
                i = 1; j = k
            elseif k <= w+h-1
                i = k-w+1; j = w
            elseif k <= w+h+w-2
                i = h; j = w-(k-w-h+1)
            else
                i = h-(k-w-h-w+2); j = 1
            end
        
            if bim[i,j] != 0
                if !on
                    on = true
                    push!(frags,[[j,i],[w÷2,h÷2]])
                end
            else
                if on # right side of stroke, average to get center of stroke
                    on = false
                    frags[end][1][1] = (frags[end][1][1]+lj)÷2
                    frags[end][1][2] = (frags[end][1][2]+li)÷2
                end
            end
            li = i
            lj = j
        end
        if length(frags) == 2
            frags = [[frags[1][1],frags[2][1]]]
        elseif length(frags) > 2
            ms = 0
            mi = -1
            mj = -1
            # use convolution to find brightest blob
            for i=2:h-1
                for j=2:w-1
                    s = 
                       (bim[i-1,j-1]) + (bim[i-1,j]) +(bim[i-1,j+1])+
                       (bim[i,j-1]  ) +   (bim[i,j]) +    (bim[i,j+1])+
                       (bim[i+1,j-1]) + (bim[i+1,j]) +  (bim[i+1,j+1]);
                    if s > ms
                        mi = i
                        mj = j
                        ms = s
                    elseif s == ms && abs(j-(w÷2))+abs(i-(h÷2)) < abs(mj-(w÷2))+abs(mi-(h÷2))
                        mi = i
                        mj = j
                        ms = s
                    end
                end
            end
            if mi != -1
                for i=1:length(frags)
                    frags[i][2] = [mj,mi]
                end 
            end
        end
        return frags
    end
    
    function trace_skeleton(bim,csize,maxiter)
    
        h,w = size(bim)
        if maxiter <= 0
            return []
        end
        if w <= csize && h <= csize
            return chunk_to_frags(bim)
        end
        ms = w+h
        mi = -1
        mj = -1
        if h > csize
            for i=4:h-3
                if bim[i,1]>0 || bim[i-1,1]>0 || bim[i,w]>0 || bim[i-1,w]>0
                    continue
                end
                s = 0
                for j=1:w
                    s+=bim[i,j]
                    s+=bim[i-1,j]
                end
                if s < ms
                    ms = s; mi = i
                elseif s == ms && abs(i-h÷2)<abs(mi-h÷2)
                    # if there is a draw (very common), we want the seam to be near the middle
                    # to balance the divide and conquer tree
                    ms = s; mi = i
                end
            end
        end
        if w > csize
            for j=4:w-3
                if bim[1,j]>0 || bim[h,j]>0 || bim[1,j-1]>0 || bim[h,j-1]>0
                    continue
                end
                s = 0
                for i=1:h
                    s+=bim[i,j]
                    s+=bim[i,j-1]
                end
                if s < ms
                    ms = s;
                    mi = -1
                    mj = j
                elseif s == ms && abs(j-w÷2)<abs(mj-w÷2)
                    ms = s
                    mi = -1
                    mj = j
                end
            end
        end
        dr = 0
        sx = 0
        L = []
        R = []
    
        if h > csize && mi != -1
            L = view(bim, 1:mi-1,:)
            R = view(bim, mi:h,  :)
            dr = VERTICAL;
            sx = mi
        elseif w > csize && mj != -1
            L = view(bim, :, 1:mj-1)
            R = view(bim, :, mj:w)
            dr = HORIZONTAL
            sx = mj
        end
        frags = []
        if dr != 0 && sum(L)>0
            frags = trace_skeleton(L,csize,maxiter-1)
        end
        if dr != 0 && sum(R)>0
            merge_frags!(frags,trace_skeleton(R,csize,maxiter-1),sx,dr)
        end
        if mi == -1 && mj == -1
            return chunk_to_frags(bim)
        end
        return frags
    end
    
    function polylines_to_svg(q,w,h)
        svg = @sprintf("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\" fill=\"none\" stroke=\"black\" stroke-width=\"1\">",w,h)
        for i=1:length(q)
            svg *= "<path d=\""
            for j=1:length(q[i])
                svg *= @sprintf("%s%d,%d ",(j==1 ? "M" : "L"),q[i][j][1],q[i][j][2])
            end
            svg *= "\"/>"
        end
        svg *= "</svg>"
        return svg
    end
    
    # img = load(ARGS[1])
    # bim = convert(Array{UInt8}, reshape([round(img[i,j].r) for j=1:size(img)[2] for i=1:size(img)[1]], size(img)))
    
    # print("<!-- ")
    # @time bim = thinning_zs(bim)
    # H,W = size(bim)
    # @time q = trace_skeleton(bim,10,999)
    # print(" -->")
    # print(polylines_to_svg(q,W,H))
    
    # using ColorTypes
    # img = convert(Array{ColorTypes.Gray{Bool}}, convert(Array{Bool},bim))
    # save("out.jpg",img)