diff --git a/README.md b/README.md index 0374a9e..bd656ba 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ paint(wc, "alice.png", ratio=0.5, background=outline(wc.mask, color="purple", li ![animation](res/animation.gif) [Training animation](./examples/animation.jl) *Run the command `runexample(:animation)` or `showexample(:animation)` to get the result.* -## Specifying the style of a particular word -![specifiedstyle](res/specifiedstyle.png) -[Speciying the style of a particular word](./examples/specifiedstyle.jl) -*Run the command `runexample(:specifiedstyle)` or `showexample(:specifiedstyle)` to get the result.* +## Gathering style +![gathering](res/gathering.png) +[gathering style](./examples/gathering.jl) +*Run the command `runexample(:gathering)` or `showexample(:gathering)` to get the result.* ## Comparison ![compare](res/compare.png) [Comparison of Obama's and Trump's inaugural address](./examples/compare.jl) diff --git "a/examples/\344\270\255\346\226\207.jl" "b/examples/\344\270\255\346\226\207.jl" index 5f29877..2063bf5 100644 --- "a/examples/\344\270\255\346\226\207.jl" +++ "b/examples/\344\270\255\346\226\207.jl" @@ -21,10 +21,12 @@ jieba.add_word("英特纳雄耐尔") wc = wordcloud( processtext(jieba.lcut(TheInternationale)), colors = "#DE2910", - mask = WordCloud.randommask("#FFDE00", 400), - density=0.65)|>generate! -println("结果保存在 中文.svg") -paint(wc, "中文.svg") +# mask = WordCloud.randommask("#FFDE00", 400), + mask = loadmask(pkgdir(WordCloud)*"/res/heart_mask.png", color="#FFDE00"), + density=0.65) |> generate! + +println("结果保存在 中文.png") +paint(wc, "中文.png") wc #eval# runexample(:中文) -#md# ![](中文.svg) \ No newline at end of file +#md# ![](中文.png) \ No newline at end of file diff --git a/res/gathering.png b/res/gathering.png new file mode 100644 index 0000000..18544cd Binary files /dev/null and b/res/gathering.png differ diff --git a/res/heart_mask.png b/res/heart_mask.png new file mode 100644 index 0000000..4f554b8 Binary files /dev/null and b/res/heart_mask.png differ diff --git a/src/qtree.jl b/src/qtree.jl index 9ad4934..03bb32a 100644 --- a/src/qtree.jl +++ b/src/qtree.jl @@ -1,8 +1,8 @@ module QTree export AbstractStackedQtree, StackedQtree, ShiftedQtree, buildqtree!, shift!, setrshift!, setcshift!, setshift!, getshift, getcenter, setcenter!, - collision, collision_bfs, collision_bfs_rand, listcollision, - findroom_rand, findroom_gathering, levelnum, outofbounds, kernelsize, placement!, decode + collision, collision_bfs, collision_bfs_rand, batchcollision, + findroom_uniform, findroom_gathering, levelnum, outofbounds, kernelsize, placement!, decode using Random using Combinatorics @@ -206,7 +206,7 @@ function shift!(t::ShiftedQtree, l::Integer, st1::Integer, st2::Integer) end buildqtree!(t, l + 1) end -shift!(t::ShiftedQtree, l::Integer, st::Tuple{Integer,Integer}) = shift!(t, l, st...) +shift!(t::ShiftedQtree, l::Integer, st::Tuple{Integer, Integer}) = shift!(t, l, st...) function setshift!(t::ShiftedQtree, l::Integer, st1::Integer, st2::Integer) for i in l:-1:1 setrshift!(t[i], st1) @@ -216,11 +216,13 @@ function setshift!(t::ShiftedQtree, l::Integer, st1::Integer, st2::Integer) end buildqtree!(t, l + 1) end -setshift!(t::ShiftedQtree, l::Integer, st::Tuple{Integer,Integer}) = setshift!(t, l, st...) -setshift!(t::ShiftedQtree, st::Tuple{Integer,Integer}) = setshift!(t, 1, st) +setshift!(t::ShiftedQtree, l::Integer, st::Tuple{Integer, Integer}) = setshift!(t, l, st...) +setshift!(t::ShiftedQtree, st::Tuple{Integer, Integer}) = setshift!(t, 1, st) getshift(t::ShiftedQtree, l::Integer=1) = getshift(t[l]) kernelsize(t::ShiftedQtree, l::Integer=1) = kernelsize(t[l]) getcenter(t::ShiftedQtree) = getshift(t) .+ kernelsize(t) .÷ 2 +getcenter(l::Integer, a::Integer, b::Integer) = l == 1 ? (a, b) : (2^(l-1)*(a-1)+2^(l-2), 2^(l-1)*(b-1)+2^(l-2)) +getcenter(ind::Tuple{Integer, Integer, Integer}) = getcenter(ind...) callefttop(t::ShiftedQtree, center) = center .- kernelsize(t) .÷ 2 setcenter!(t::ShiftedQtree, center) = setshift!(t, callefttop(t, center)) function inbounds(bgqt::ShiftedQtree, qt::ShiftedQtree) diff --git a/src/qtreetools.jl b/src/qtreetools.jl index 7a116ca..4aaffc3 100644 --- a/src/qtreetools.jl +++ b/src/qtreetools.jl @@ -90,7 +90,7 @@ function collision_bfs_rand(Q1::AbstractStackedQtree, Q2::AbstractStackedQtree, end ColItemType = Pair{Tuple{Int,Int},Tuple{Int,Int,Int}} -function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, +function batchcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, indpairs::Vector{<:Union{Vector, Tuple}}; collist=Vector{ColItemType}(), queue=Vector{Tuple{Int,Int,Int}}(), at=(levelnum(qtrees[1]), 1, 1)) getqtree(i) = i==0 ? mask : qtrees[i] @@ -104,7 +104,7 @@ function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree end collist end -function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, +function batchcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, indpairs::Vector{Tuple{Tuple{Int,Int},Tuple{Int,Int,Int}}}; collist=Vector{ColItemType}(), queue=Vector{Tuple{Int,Int,Int}}()) getqtree(i) = i==0 ? mask : qtrees[i] @@ -118,18 +118,18 @@ function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree end collist end -function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, +function batchcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, inds=0:length(qtrees); collist=Vector{ColItemType}(), queue=Vector{Tuple{Int,Int,Int}}(), at=(levelnum(qtrees[1]), 1, 1)) indpairs = combinations(inds, 2) |> collect |> shuffle! - listcollision_native(qtrees, mask, indpairs, collist=collist, at=at) + batchcollision_native(qtrees, mask, indpairs, collist=collist, at=at) end -function listcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, +function batchcollision_native(qtrees::AbstractVector, mask::AbstractStackedQtree, inds::AbstractSet; kargs...) - listcollision_native(qtrees, mask, inds|>collect; kargs...) + batchcollision_native(qtrees, mask, inds|>collect; kargs...) end -function findroom_rand(ground, q=[(levelnum(ground), 1, 1)]) +function findroom_uniform(ground, q=[(levelnum(ground), 1, 1)]) if isempty(q) push!(q, (levelnum(ground), 1, 1)) end @@ -253,6 +253,7 @@ end "将sortedtrees依次叠加到ground上,同时修改sortedtrees的shift" function placement!(ground, sortedtrees; kargs...) # pos = Vector{Tuple{Int, Int, Int}}() + ind = nothing for t in sortedtrees ind = placement!(ground, t; kargs...) overlap!(ground, t) @@ -261,21 +262,17 @@ function placement!(ground, sortedtrees; kargs...) end # push!(pos, ind) end - nothing + ind # return pos end -function placement!(ground, qtree::ShiftedQtree; roomfinder=findroom_rand, kargs...) +function placement!(ground, qtree::ShiftedQtree; roomfinder=findroom_uniform, kargs...) ind = roomfinder(ground; kargs...) # @show ind if ind === nothing return nothing end - l, r, c = ind - x = floor(2^(l - 1) * (r - 1) + 2^(l - 2)) - y = floor(2^(l - 1) * (c - 1) + 2^(l - 2)) - m, n = kernelsize(qtree[1]) - setshift!(qtree, 1, x - m ÷ 2, y - n ÷ 2) # 居中 + setcenter!(qtree, getcenter(ind)) # 居中 return ind end @@ -296,11 +293,13 @@ function placement!(ground, sortedtrees, indexes; kargs...) end overlap!(ground, sortedtrees[i]) end + ind = nothing for i in indexes - placement!(ground, sortedtrees[i]; kargs...) + ind = placement!(ground, sortedtrees[i]; kargs...) + if ind === nothing return ind end overlap!(ground, sortedtrees[i]) end - nothing + ind end function locate(qt::AbstractStackedQtree, ind::Tuple{Int, Int, Int}=(levelnum(qt), 1, 1)) @@ -367,7 +366,7 @@ function locate!(qts::AbstractVector, inds::Union{AbstractVector{Int}, AbstractS end -function listcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, loctree::QtreeNode; +function batchcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, loctree::QtreeNode; collist = Vector{ColItemType}(), queue = Vector{Tuple{Int, Int, Int}}(), ) @@ -379,12 +378,12 @@ function listcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, # @show length(loctree.value.loc), length(loctree.value.cumloc) indpairs = combinations(loctree.value.loc, 2) |> collect indpairs = [(min(p...), max(p...)) for p in indpairs] |> shuffle! - listcollision_native(qtrees, mask, indpairs, collist=collist, queue=queue, at=loctree.value.ind) + batchcollision_native(qtrees, mask, indpairs, collist=collist, queue=queue, at=loctree.value.ind) end if length(loctree.value.loc) > 0 && length(loctree.value.cumloc) > 0 indpairs = Iterators.product(loctree.value.cumloc, loctree.value.loc) |> collect |> vec indpairs = [(min(p...), max(p...)) for p in indpairs] |> shuffle! - listcollision_native(qtrees, mask, indpairs, collist=collist, queue=queue, at=loctree.value.ind) + batchcollision_native(qtrees, mask, indpairs, collist=collist, queue=queue, at=loctree.value.ind) end for c in loctree.children if c !== nothing @@ -394,21 +393,21 @@ function listcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, end collist end -function listcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree; kargs...) +function batchcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree; kargs...) loctree = locate!(qtrees) loctree = locate!(mask, loctree, label=0, newnode=LocQtreeInt) - listcollision_qtree(qtrees, mask, loctree; kargs...) + batchcollision_qtree(qtrees, mask, loctree; kargs...) end -function listcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, inds::Union{AbstractVector{Int}, AbstractSet{Int}}; kargs...) +function batchcollision_qtree(qtrees::AbstractVector, mask::AbstractStackedQtree, inds::Union{AbstractVector{Int}, AbstractSet{Int}}; kargs...) loctree = locate!(qtrees, inds) loctree = locate!(mask, loctree, label=0, newnode=LocQtreeInt) - listcollision_qtree(qtrees, mask, loctree; kargs...) + batchcollision_qtree(qtrees, mask, loctree; kargs...) end -function listcollision(qtrees::AbstractVector, mask::AbstractStackedQtree, args...; kargs...) +function batchcollision(qtrees::AbstractVector, mask::AbstractStackedQtree, args...; kargs...) if length(qtrees) > 25 - return listcollision_qtree(qtrees, mask, args...; kargs...) + return batchcollision_qtree(qtrees, mask, args...; kargs...) else - return listcollision_native(qtrees, mask, args...; kargs...) + return batchcollision_native(qtrees, mask, args...; kargs...) end end diff --git a/src/rendering.jl b/src/rendering.jl index a590975..144fa0b 100644 --- a/src/rendering.jl +++ b/src/rendering.jl @@ -6,7 +6,7 @@ using Luxor using Colors using ColorSchemes using ImageMagick -import ImageTransformations.filter +import ImageTransformations.imresize save = Luxor.FileIO.save @@ -225,7 +225,7 @@ schemes_seaborn = filter(s -> occursin("seaborn", colorschemes[s].category), col schemes = [schemes_colorbrewer..., schemes_seaborn...] """ -get box or ellipse image +get a box or ellipse svg image ## Examples * shape(box, 80, 50) #80*50 box * shape(box, 80, 50, 4) #box with cornerradius=4 diff --git a/src/strategy.jl b/src/strategy.jl index 28b8b35..03e6c69 100644 --- a/src/strategy.jl +++ b/src/strategy.jl @@ -24,12 +24,13 @@ function feelingoccupied(imgs, border=0, bgvalue=imgs[1][1]) sum(s) - er end -function textoccupied(words, fontsizes, fonts; border=0) +function textoccupied(words, fontsizes, fonts) + border=1 imgs = [] for (c, sz, ft) in zip(words, fontsizes, fonts) # print(c) - img = Render.rendertext(string(c), sz, backgroundcolor=(0,0,0,0),font=ft, border=border) - push!(imgs, wordmask(img, (0,0,0,0), border)) + img = Render.rendertext(string(c), sz, backgroundcolor=(0,0,0,0), font=ft, border=border) + push!(imgs, img) end feelingoccupied(imgs, border) #border>0 以获取背景色imgs[1] end @@ -57,7 +58,7 @@ function preparebackground(img, bgcolor) return img, maskqt, groundsize, groundoccupied end -function prepareword(word, fontsize, color, angle, groundsize; backgroundcolor=(0,0,0,0), font="", border=0) +function prepareword(word, fontsize, color, angle; backgroundcolor=(0,0,0,0), font="", border=0) rendertext(string(word), fontsize, color=color, backgroundcolor=backgroundcolor, angle=angle, border=border, font=font, type=:both) end @@ -66,17 +67,17 @@ wordmask(img, bgcolor, border) = dilate(img.!=img[1], border) #https://github.com/JuliaGraphics/Luxor.jl/issues/107 ## weight_scale -function cal_weight_scale(words, fontsizes, fonts, target, initialscale; kargs...) +function cal_weight_scale(words, fontsizes, fonts, target, initialscale) input = initialscale - output = textoccupied(words, fontsizes, fonts; kargs...) + output = textoccupied(words, fontsizes, fonts) return output, sqrt(target/output) * input# 假设output=k*input^2 end -function find_weight_scale!(wc::WC; initialscale=0, density=0.3, maxiter=5, error=0.05, kargs...) +function find_weight_scale!(wc::WC; initialscale=0, density=0.3, maxiter=5, error=0.05) ground_size = wc.params[:groundoccupied] words = wc.words if initialscale <= 0 - initialscale = √(ground_size/length(words)/0.4*density) + initialscale = √(ground_size/length(words)/0.4*density) #初始值假设字符的字面框面积占正方格比率为0.4(低估了汉字) end @assert sum(wc.weights.^2 .* length.(words)) / length(wc.weights) ≈ 1.0 target_lower = (density - error) * ground_size @@ -91,8 +92,7 @@ function find_weight_scale!(wc::WC; initialscale=0, density=0.3, maxiter=5, erro break end wc.params[:scale] = sc - tg, sc = cal_weight_scale(words, getfontsizes(wc, words), fonts, - density*ground_size, sc; kargs...) + tg, sc = cal_weight_scale(words, getfontsizes(wc, words), fonts, density*ground_size, sc) println("scale=$(wc.params[:scale]), density=$(tg/ground_size)") if target_lower <= tg <= target_upper break diff --git a/src/train.jl b/src/train.jl index 91ac87f..b798b19 100644 --- a/src/train.jl +++ b/src/train.jl @@ -126,7 +126,7 @@ trainepoch_E!(tr_ma) = Dict(:collpool=>Vector{QTree.ColItemType}(), :queue=>Vect trainepoch_E!(s::Symbol) = get(Dict(:patient=>10, :nepoch=>1000), s, nothing) function trainepoch_E!(qtrees, mask; optimiser=(t, Δ)->Δ./4, queue=Vector{Tuple{Int, Int, Int}}(), collpool=trainepoch_E!(:collpool)) - listcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) nc = length(collpool) if nc == 0 return nc end step_inds!(mask, qtrees, collpool, optimiser) @@ -134,7 +134,7 @@ function trainepoch_E!(qtrees, mask; optimiser=(t, Δ)->Δ./4, pop!(inds,0, 0) # @show length(qtrees),length(inds) for ni in 1:length(qtrees)÷length(inds) - listcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni > 8length(collpool) break end end @@ -148,7 +148,7 @@ trainepoch_EM!(tr_ma) = Dict(:collpool=>Vector{QTree.ColItemType}(), trainepoch_EM!(s::Symbol) = get(Dict(:patient=>10, :nepoch=>1000), s, nothing) function trainepoch_EM!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, queue=Vector{Tuple{Int, Int, Int}}(), collpool=Vector{QTree.ColItemType}()) - listcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) nc = length(collpool) if nc == 0 return nc end step_inds!(mask, qtrees, collpool, optimiser) @@ -158,14 +158,14 @@ function trainepoch_EM!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds) inds = take(memory, length(inds)*2) for ni in 1:length(qtrees)÷length(inds) - listcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni > 4length(collpool) break end inds2 = first.(collpool)|>Iterators.flatten|>Set pop!(inds2,0, 0) # @show length(qtrees),length(inds),length(inds2) for ni2 in 1:length(inds)÷length(inds2) - listcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni2 > 8length(collpool) break end end @@ -177,7 +177,7 @@ trainepoch_EM2!(tr_ma) = trainepoch_EM!(tr_ma) trainepoch_EM2!(s::Symbol) = trainepoch_EM!(s) function trainepoch_EM2!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, queue=Vector{Tuple{Int, Int, Int}}(), collpool=Vector{QTree.ColItemType}()) - listcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) nc = length(collpool) if nc == 0 return nc end step_inds!(mask, qtrees, collpool, optimiser) @@ -187,7 +187,7 @@ function trainepoch_EM2!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds) inds = take(memory, length(inds)*4) for ni in 1:length(qtrees)÷length(inds) - listcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni > 4length(collpool) break end inds2 = first.(collpool)|>Iterators.flatten|>Set @@ -195,14 +195,14 @@ function trainepoch_EM2!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds2) inds2 = take(memory, length(inds2)*2) for ni2 in 1:length(inds)÷length(inds2) - listcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni2 > 4length(collpool) break end inds3 = first.(collpool)|>Iterators.flatten|>Set pop!(inds3,0, 0) # @show length(qtrees),length(inds),length(inds2),length(inds3) for ni3 in 1:length(inds2)÷length(inds3) - listcollision(qtrees, mask, inds3, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds3, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni3 > 8length(collpool) break end end @@ -216,7 +216,7 @@ trainepoch_EM3!(tr_ma) = trainepoch_EM!(tr_ma) trainepoch_EM3!(s::Symbol) = trainepoch_EM!(s) function trainepoch_EM3!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, queue=Vector{Tuple{Int, Int, Int}}(), collpool=Vector{QTree.ColItemType}()) - listcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, queue=queue, collist=empty!(collpool)) nc = length(collpool) if nc == 0 return nc end step_inds!(mask, qtrees, collpool, optimiser) @@ -226,7 +226,7 @@ function trainepoch_EM3!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds) inds = take(memory, length(inds)*8) for ni in 1:length(qtrees)÷length(inds) - listcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni > 4length(collpool) break end inds2 = first.(collpool)|>Iterators.flatten|>Set @@ -234,7 +234,7 @@ function trainepoch_EM3!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds2) inds2 = take(memory, length(inds2)*4) for ni2 in 1:length(inds)÷length(inds2) - listcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds2, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni2 > 4length(collpool) break end inds3 = first.(collpool)|>Iterators.flatten|>Set @@ -242,14 +242,14 @@ function trainepoch_EM3!(qtrees, mask; memory, optimiser=(t, Δ)->Δ./4, push!.(memory, inds3) inds3 = take(memory, length(inds3)*2) for ni3 in 1:length(inds2)÷length(inds3) - listcollision(qtrees, mask, inds3, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds3, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni3 > 4length(collpool) break end inds4 = first.(collpool)|>Iterators.flatten|>Set pop!(inds4,0, 0) # @show length(qtrees),length(inds),length(inds2),length(inds3) for ni4 in 1:length(inds3)÷length(inds4) - listcollision(qtrees, mask, inds4, queue=queue, collist=empty!(collpool)) + batchcollision(qtrees, mask, inds4, queue=queue, collist=empty!(collpool)) step_inds!(mask, qtrees, collpool, optimiser) if ni4 > 8length(collpool) break end end diff --git a/src/wc-helper.jl b/src/wc-helper.jl index c86e535..c03bdf6 100644 --- a/src/wc-helper.jl +++ b/src/wc-helper.jl @@ -28,6 +28,7 @@ function randommask(color, sz=800) ratio = ratio>0.9 ? 1.0 : ratio h = round(Int, sqrt(s*ratio)) w = round(Int, h/ratio) + println("mask size is $((h, w))") if rand() > 0.5 return shape(box, w, h, round(Int, h*(0.05+rand()/5)), color=color, backgroundcolor=ARGB(1, 1, 1, 0)) else diff --git a/src/wc-method.jl b/src/wc-method.jl index 0c876ac..6030330 100644 --- a/src/wc-method.jl +++ b/src/wc-method.jl @@ -12,7 +12,7 @@ initqtree!(wc, i; kargs...) = initqtree!.(wc, index(wc, i); kargs...) function initimage!(wc, i::Integer; backgroundcolor=(0,0,0,0), border=wc.params[:border]) params = wc.params img, svg = prepareword(wc.words[i], getfontsizes(wc, i), params[:colors][i], params[:angles][i], - params[:groundsize], font=getfonts(wc, i), backgroundcolor=backgroundcolor, border=border) + font=getfonts(wc, i), backgroundcolor=backgroundcolor, border=border) wc.imgs[i] = img wc.svgs[i] = svg initqtree!(wc, i, backgroundcolor=backgroundcolor, border=border) @@ -45,26 +45,29 @@ initimages! = initimage! * placement!(wc, style=:uniform) * placement!(wc, style=:gathering) * placement!(wc, style=:gathering, level=5) #`level` controls the intensity of gathering, typically between 4 and 6, defaults to 5. -* placement!(wc, style=:gathering, level=6, p=1) #`p` refers to p-norm (Minkowski distance), defaults to 2. +* placement!(wc, style=:gathering, level=6, p=4) #`p` refers to p-norm (Minkowski distance), defaults to 2. p=1 produces a rhombus, p=2 produces an ellipse, p>2 produces a rectangle with rounded corners. -When setting `style=:gathering`, you need to disable teleport (`generate!(wc, patient=-1)`). +When you have set `style=:gathering`, you should disable teleporting in ``generate!` at the same time(`generate!(wc, patient=-1)`). """ function placement!(wc::WC; style=:uniform, kargs...) if getstate(wc) == nameof(wordcloud) initimages!(wc) end @assert style in [:uniform, :gathering] - if style == :gathering - if wc.maskqtree[1][(wc.params[:groundsize].÷2)] == EMPTY && (length(wc.qtrees)<2 - || (length(wc.qtrees)>=2 && prod(kernelsize(wc.qtrees[2]))/prod(kernelsize(wc.qtrees[1])) < 0.5)) - setcenter!(wc.qtrees[1], wc.params[:groundsize] .÷ 2) - QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, 2:length(wc.qtrees)|>collect, - roomfinder=findroom_gathering; kargs...) + if length(wc.qtrees) > 0 + if style == :gathering + if wc.maskqtree[1][(wc.params[:groundsize].÷2)] == EMPTY && (length(wc.qtrees)<2 + || (length(wc.qtrees)>=2 && prod(kernelsize(wc.qtrees[2]))/prod(kernelsize(wc.qtrees[1])) < 0.5)) + setcenter!(wc.qtrees[1], wc.params[:groundsize] .÷ 2) + ind = QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, 2:length(wc.qtrees)|>collect, + roomfinder=findroom_gathering; kargs...) + else + ind = QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, roomfinder=findroom_gathering; kargs...) + end else - QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, roomfinder=findroom_gathering; kargs...) + ind = QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, roomfinder=findroom_uniform; kargs...) end - else - QTree.placement!(deepcopy(wc.maskqtree), wc.qtrees, roomfinder=findroom_rand; kargs...) + if ind === nothing error("no room for placement") end end wc.params[:state] = nameof(placement!) wc @@ -85,8 +88,8 @@ end * wc: the wordcloud to train * nepoch: training epoch nums # Keyword Args -* retry: shrink & retrain times, default 3 -* patient: number of epochs before teleporting +* retry: shrink & retrain times, defaults to 3, set to `1` to disable shrinking +* patient: number of epochs before teleporting, set to `-1` to disable teleporting * trainer: appoint a training engine """ function generate!(wc::WC, args...; retry=3, krags...) @@ -96,9 +99,12 @@ function generate!(wc::WC, args...; retry=3, krags...) ep, nc = -1, -1 for r in 1:retry if r != 1 - rescale!(wc, 0.95) + rescale!(wc, 0.97) + dens = textoccupied(getwords(wc), getfontsizes(wc), getfonts(wc))/wc.params[:groundoccupied] + println("#$r. try scale = $(wc.params[:scale]). The density is reduced to $dens") + else + print("#$r. scale = $(wc.params[:scale])") end - println("#$r. scale = $(wc.params[:scale])") ep, nc = train!(wc.qtrees, wc.maskqtree, args...; krags...) wc.params[:epoch] += ep if nc == 0 @@ -110,7 +116,7 @@ function generate!(wc::WC, args...; retry=3, krags...) wc.params[:state] = nameof(generate!) @assert isempty(outofbounds(wc.maskqtree, wc.qtrees)) else #check - colllist = first.(listcollision(wc.qtrees, wc.maskqtree)) + colllist = first.(batchcollision(wc.qtrees, wc.maskqtree)) get_text(i) = i>0 ? wc.words[i] : "#MASK#" collwords = [(get_text(i), get_text(j)) for (i,j) in colllist] if length(colllist) > 0 diff --git a/test/runtests.jl b/test/runtests.jl index d82762b..1ab0b8c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,6 +31,14 @@ include("test_textprocessing.jl") @test wc.params[:groundoccupied] == WordCloud.occupied(WordCloud.QTree.kernel(wc.maskqtree[1]), WordCloud.QTree.FULL) @test wc.params[:groundoccupied] == WordCloud.occupied(wc.mask .!= wc.mask[1]) + words = ["." for i in 1:500] + weights = [1 for i in 1:length(words)] + + @test_throws ErrorException begin + wc = wordcloud(words, weights, mask=shape(ellipse, 5, 5, color=0.95, backgroundsize=(10,10)), density=1000, angles=0) + placement!(wc) + end + wc = wordcloud( processtext(open("../res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"], maxnum=300), mask = loadmask("../res/alice_mask.png", color="#faeef8", backgroundcolor=0.97), diff --git a/test/test_qtree.jl b/test/test_qtree.jl index 61a242f..04666e2 100644 --- a/test/test_qtree.jl +++ b/test/test_qtree.jl @@ -32,7 +32,7 @@ testqtree = WordCloud.testqtree words = [Random.randstring(rand(1:8)) for i in 1:rand(100:1000)] weights = randexp(length(words)) .* 1000 .+ randexp(length(words)) .* 200 .+ rand(20:100, length(words)); wc = wordcloud(words, weights, density=0.7) - clq = WordCloud.QTree.listcollision_qtree(wc.qtrees, wc.maskqtree) - cln = WordCloud.QTree.listcollision_native(wc.qtrees, wc.maskqtree) + clq = WordCloud.QTree.batchcollision_qtree(wc.qtrees, wc.maskqtree) + cln = WordCloud.QTree.batchcollision_native(wc.qtrees, wc.maskqtree) @test Set(first.(clq)) == Set(first.(cln)) end \ No newline at end of file