From ea43e10c68daa22c32a3369fb8e9b136275cff6d Mon Sep 17 00:00:00 2001 From: guoyongzhi Date: Mon, 18 Jan 2021 20:34:44 +0800 Subject: [PATCH] more interfaces & more documents --- .gitignore | 5 +- README.md | 29 ++++++---- examples/alice.jl | 3 +- examples/animation.jl | 4 +- examples/compare.jl | 58 +++++++++---------- examples/specifystyle.jl | 19 +++--- src/WordCloud.jl | 7 ++- src/interface.jl | 122 ++++++++++++++++++++++++++++----------- src/nlp.jl | 18 +++++- src/qtree.jl | 11 ++-- src/utils.jl | 24 +++++++- test/runtests.jl | 11 +++- test/test_qtree.jl | 26 +-------- 13 files changed, 214 insertions(+), 123 deletions(-) diff --git a/.gitignore b/.gitignore index 2dbc723..011f314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .ipynb_checkpoints .vscode +*.cov res/*/* address_compare -guxiang_animation \ No newline at end of file +guxiang_animation +/*.png +test/*.jpg diff --git a/README.md b/README.md index 4dcd5a7..d8d7df5 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,12 @@ wordcloud in Julia * **Exact** Words with the same weight have the exact same size. The algorithm will never scale the word to fit the blank. --- - +# Installation +```julia +import Pkg; Pkg.add("WordCloud") +``` # Basic Usage ```julia -]add WordCloud using WordCloud words = "天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔龙师火帝鸟官人皇始制文字乃服衣裳推位让国有虞陶唐吊民伐罪周发殷汤坐朝问道垂拱平章" words = [string(c) for c in words] @@ -17,27 +19,34 @@ wc = wordcloud(words, weights) generate!(wc) paint(wc, "qianziwen.png") ``` -*Run the command `runexample(:qianziwen)` to get the result.* +*Run the command `runexample(:qianziwen)` or `showexample(:qianziwen)` to get the result.* # More Complex Usage ```julia +using WordCloud wc = wordcloud( process(open(pkgdir(WordCloud)*"/res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"]), mask = loadmask(pkgdir(WordCloud)*"/res/alice_mask.png", color="#faeef8"), - colors = (WordCloud.colorschemes[:Set1_5].colors..., ), + colors = :Set1_5, angles = (0, 90), fillingrate = 0.7) |> generate! -paint(wc, "alice.png", ratio=0.5) +paint(wc, "alice.png", ratio=0.5, background=outline(wc.mask, color="purple", linewidth=1)) ``` -*Run the command `runexample(:alice)` to get the result.* +*Run the command `runexample(:alice)` or `showexample(:alice)` to get the result.* ![alice](res/alice.png) -# More +# More Examples +## Training animation ![animation](res/animation.gif) -[Training Animation](./examples/animation.jl) -*Run the command `runexample(:animation)` to get the result.* +[Training animation](./examples/animation.jl) +*Run the command `runexample(:animation)` or `showexample(:animation)` to get the result.* +## Specifies the style of a particular word +![specifystyle](res/specifystyle.png) +[Specifies the style of a particular word](./examples/specifystyle.jl) +*Run the command `runexample(:specifystyle)` or `showexample(:specifystyle)` to get the result.* +## Comparison ![compare](res/compare.png) [Comparison of Obama's and Trump's inaugural address](./examples/compare.jl) -*Run the command `runexample(:compare)` to get the result.* +*Run the command `runexample(:compare)` or `showexample(:compare)` to get the result.* *** * [x] 排序 & 预放置 diff --git a/examples/alice.jl b/examples/alice.jl index 586c1a7..ff7776f 100644 --- a/examples/alice.jl +++ b/examples/alice.jl @@ -2,8 +2,9 @@ using WordCloud wc = wordcloud( process(open(pkgdir(WordCloud)*"/res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"]), mask = loadmask(pkgdir(WordCloud)*"/res/alice_mask.png", color="#faeef8"), - colors = (WordCloud.colorschemes[:Set1_5].colors..., ), + colors = :Set1_5, angles = (0, 90), fillingrate = 0.7) |> generate! +println("save results to alice.png") paint(wc, "alice.png", background=outline(wc.mask, color="purple", linewidth=1)) wc \ No newline at end of file diff --git a/examples/animation.jl b/examples/animation.jl index f6e2a97..fab4370 100644 --- a/examples/animation.jl +++ b/examples/animation.jl @@ -7,7 +7,7 @@ texts = df[!, "Column2"] weights = df[!, "Column3"] wc = wordcloud(texts, weights, fillingrate=0.8) - -gifdirectory = "gg/guxiang_animation" +println("save results to guxiang_animation") +gifdirectory = "guxiang_animation" generate_animation!(wc, 100, outputdir=gifdirectory) wc \ No newline at end of file diff --git a/examples/compare.jl b/examples/compare.jl index 78d4cf3..aaf497c 100644 --- a/examples/compare.jl +++ b/examples/compare.jl @@ -1,6 +1,7 @@ using WordCloud stwords = ["us", "will"]; + println("==Obama's==") cs = WordCloud.randomscheme() as = WordCloud.randomangles() @@ -10,42 +11,42 @@ wca = wordcloud( colors = cs, angles = as, fillingrate = fr) |> generate! + println("==Trump's==") -tb, wb = process(open(pkgdir(WordCloud)*"/res/Donald Trump's Inaugural Address.txt"), stopwords=WordCloud.stopwords_en ∪ stwords) -samemask = tb .∈ Ref(wca.words) -println(sum(samemask), " same words") -csb = Iterators.take(WordCloud.iter_expand(cs), length(tb)) |> collect -asb = Iterators.take(WordCloud.iter_expand(as), length(tb)) |> collect -wainds = Dict(zip(wca.words, Iterators.countfrom(1))) -for i in 1:length(tb) - if samemask[i] - ii = wainds[tb[i]] - csb[i] = wca.params[:colors][ii] - asb[i] = wca.params[:angles][ii] - end -end wcb = wordcloud( - (tb,wb), - mask = wca.mask, - colors = csb, - angles = asb, - fillingrate = fr) -for i in 1:length(tb) - if samemask[i] - ii = wainds[tb[i]] - cxy = WordCloud.QTree.center(wca.qtrees[ii]) - WordCloud.QTree.setcenter!(wcb.qtrees[i], cxy) - end + process(open(pkgdir(WordCloud)*"/res/Donald Trump's Inaugural Address.txt"), stopwords=WordCloud.stopwords_en ∪ stwords), + mask = getmask(wca), + colors = cs, + angles = as, + fillingrate = fr, + run = x->nothing, #turn off the useless initword! and placement! in advance +) + +samewords = getword(wca) ∩ getword(wcb) +println(length(samewords), " same words") + +for w in samewords + setcolor!(wcb, w, getcolor(wca, w)) + setangle!(wcb, w, getangle(wca, w)) end +#Follow these steps to generate result: initword! -> placement! -> generate! +initword!(wcb) + println("=ignore defferent words=") -ignore(wcb, .!samemask) do +ignore(wcb, getword(wcb) .∉ Ref(samewords)) do + @assert Set(wcb.words) == Set(samewords) + centers = getposition.(wca, samewords, type=getcenter) + setposition!.(wcb, samewords, centers, type=setcenter!) #manually initialize the position, + setstate!(wcb, :placement!) #and set the state flag generate!(wcb, 1000, patient=-1, retry=1) #patient=-1 means no teleport; retry=1 means no rescale end + println("=pin same words=") -pin(wcb, samemask) do +pin(wcb, samewords) do placement!(wcb) generate!(wcb, 1000, retry=1) #allow teleport but don‘t allow rescale end + if getstate(wcb) != :generate! println("=overall tuning=") generate!(wcb, 1000, patient=-1, retry=2) #allow rescale but don‘t allow teleport @@ -54,10 +55,9 @@ end ma = paint(wca) mb = paint(wcb) h,w = size(ma) -space = loadmask(shape(box, w÷20, h)) -space .= WordCloud.ARGB(0,0,0,0) +space = fill(mb[1], (h, w÷20)) try mkdir("address_compare") catch end - +println("save results to address_compare") WordCloud.ImageMagick.save("address_compare/compare.png", [ma space mb]) gif = WordCloud.GIF("address_compare") diff --git a/examples/specifystyle.jl b/examples/specifystyle.jl index 7b9f928..b21c36b 100644 --- a/examples/specifystyle.jl +++ b/examples/specifystyle.jl @@ -1,20 +1,23 @@ using WordCloud wc = wordcloud( - process(open(pkgdir(WordCloud)*"/res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"], maxweight=1, maxnum=200), + process(open("res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"], maxweight=1, maxnum=300), mask = padding(shape(ellipse, 600, 500, color=(0.98, 0.97, 0.99), bgcolor=0.97), 0.1), - colors = (WordCloud.colorschemes[:seaborn_dark].colors..., ), + colors = :seaborn_dark, angles = -90:90, run=x->x, #turn off the useless initword! and placement! in advance ) -setangle!(wc, "Alice", 0) -setcolor!(wc, "Alice", "purple"); -initword!(wc, "Alice", 2size(wc.mask, 2)/length("Alice")) -setposition!(wc, "Alice", reverse((size(wc.mask) .- size(getimage(wc, "Alice"))) .÷ 2)) +setword!(wc, "Alice", "Alice in Wonderland") # replace the word 'Alice' with 'Alice in Wonderland' +setangle!(wc, "Alice in Wonderland", 0) # make it horizontal +setcolor!(wc, "Alice in Wonderland", "purple"); +initword!(wc, "Alice in Wonderland", 2size(wc.mask, 2)/length("Alice in Wonderland")) # set a big font size +setposition!(wc, 1, reverse(size(wc.mask)) .÷ 2, type=setcenter!) # center it -pin(wc, "Alice") do - initword!(wc) +pin(wc, "Alice in Wonderland") do + initword!(wc) #init inside `pin` to reset the size of other words generate!(wc) end + +println("save results to specifystyle.png") paint(wc, "specifystyle.png") wc \ No newline at end of file diff --git a/src/WordCloud.jl b/src/WordCloud.jl index 3142e6c..302b9ff 100644 --- a/src/WordCloud.jl +++ b/src/WordCloud.jl @@ -1,9 +1,10 @@ module WordCloud export wordcloud, shape, ellipse, box, paint, loadmask, process, outline, padding, train!, Momentum, generate!, generate_animation! -export record, parsecolor, placement!, imageof, bitor, take, ignore, pin, runexample, showexample -export getstate, getcolor, getangle, getweight, setcolor!, setangle!, setweight!, - getposition, setposition!, getimage, initword! +export getshift, getcenter, setshift!, setcenter! +export record, parsecolor, placement!, rescale!, imageof, bitor, take, ignore, pin, runexample, showexample +export getstate, setstate!, getcolor, getangle, getword, getweight, setcolor!, setangle!, setweight!, setword!, + getposition, setposition!, getimage, getmask, initword! include("qtree.jl") include("rendering.jl") diff --git a/src/interface.jl b/src/interface.jl index 4d6f5f7..f97877b 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -27,6 +27,7 @@ end import ImageTransformations.imresize """ +load a img as mask, recolor, or resize, etc ## examples * loadmask("res/heart.jpg") * loadmask("res/heart.jpg", 256, 256) #resize to 256*256 @@ -71,22 +72,38 @@ mutable struct wordcloud end """ -## kargs examples -### style kargs +## Positional Arguments +Positional arguments are used to specify words and weights, and can be in different forms, such as Tuple or Dict, etc. +* words::AbstractVector{<:AbstractString}, weights::AbstractVector{<:Real} +* words_weights::Tuple +* counter::AbstractDict +* counter::AbstractVector{<:Pair} +## Optional Keyword Arguments +### style keyword arguments * colors = "black" #all same color * colors = ("black", (0.5,0.5,0.7), "yellow", "#ff0000", 0.2) #choose entries randomly * colors = ["black", (0.5,0.5,0.7), "yellow", "red", (0.5,0.5,0.7), 0.2, ......] #use entries sequentially in cycle +* colors = :seaborn_dark #using a preset scheme. see `WordCloud.colorschemes` for all supported Symbol * angles = 0 #all same angle * angles = (0, 90, 45) #choose entries randomly * angles = 0:180 #choose entries randomly * angles = [0, 22, 4, 1, 100, 10, ......] #use entries sequentially in cycle -* fillingrate = 0.5 +* fillingrate = 0.5 #default 0.65 * border = 1 -### mask kargs +### mask keyword arguments * mask = loadmask("res/heart.jpg", 256, 256) #see doc of `loadmask` * mask = loadmask("res/heart.jpg", color="red", ratio=2) #see doc of `loadmask` * mask = shape(ellipse, 800, 600, color="white", bgcolor=(0,0,0,0)) #see doc of `shape` * transparentcolor = ARGB32(0,0,0,0) #set the transparent color in mask +### other keyword arguments +The keyword argument `run` is a function. It will be called after the `wordcloud` object constructed. +* run = placement! #default setting, will initialize word's position +* run = generate! #get result directly +* run = initword! #only initialize resources, such as rendering word images +* run = x->nothing #do nothing +--- +* After getting the `wordcloud` object, these steps are needed to get the result picture: initword! -> placement! -> generate! -> paint +* You can skip `placement!` and/or `initword!`, and the default action will be performed """ wordcloud(wordsweights::Tuple; kargs...) = wordcloud(wordsweights...; kargs...) wordcloud(counter::AbstractDict; kargs...) = wordcloud(keys(counter)|>collect, values(counter)|>collect; kargs...) @@ -98,7 +115,7 @@ function wordcloud(words::AbstractVector{<:AbstractString}, weights::AbstractVec @assert length(words) == length(weights) > 0 params = Dict{Symbol, Any}(kargs...) # @show params - + colors = colors isa Symbol ? (colorschemes[:seaborn_dark].colors..., ) : colors colors_o = colors colors = Iterators.take(iter_expand(colors), length(words)) |> collect params[:colors] = colors @@ -135,7 +152,7 @@ function wordcloud(words::AbstractVector{<:AbstractString}, weights::AbstractVec if minfontsize==:auto minfontsize = min(8, sqrt(groundoccupied/length(words)/8)) println("set minfontsize to $minfontsize") - @show groundoccupied, length(words) + @show groundoccupied length(words) params[:minfontsize] = minfontsize end get!(params, :border, 1) @@ -153,43 +170,66 @@ end Base.getindex(wc::wordcloud, inds...) = wc.words[inds...]=>wc.weights[inds...] Base.lastindex(wc::wordcloud) = lastindex(wc.words) +Base.broadcastable(wc::wordcloud) = Ref(wc) getstate(wc::wordcloud) = wc.params[:state] -function index(wc::wordcloud, w::AbstractString) +setstate!(wc::wordcloud, st::Symbol) = wc.params[:state] = st +function getindsmap(wc::wordcloud) if wc.params[:indsmap] === nothing wc.params[:indsmap] = Dict(zip(wc.words, Iterators.countfrom(1))) end - wc.params[:indsmap][w] + wc.params[:indsmap] +end +function index(wc::wordcloud, w::AbstractString) + getindsmap(wc)[w] +end +index(wc::wordcloud, i) = i +getdoc = "The 1st arg is a wordcloud, the 2nd arg can be a word string, a index number or a BitArray mask and ignored to return all." +setdoc = "The 1st arg is a wordcloud, the 2nd arg can be a word string or a index number, the 3rd arg is the value to assign." +@doc getdoc getcolor(wc::wordcloud, w=:) = wc.params[:colors][index(wc, w)] +@doc getdoc getangle(wc::wordcloud, w=:) = wc.params[:angles][index(wc, w)] +@doc getdoc getword(wc::wordcloud, w=:) = wc.words[index(wc, w)] +@doc getdoc getweight(wc::wordcloud, w=:) = wc.weights[index(wc, w)] +@doc setdoc setcolor!(wc::wordcloud, w, c) = wc.params[:colors][index(wc, w)] = parsecolor(c) +@doc setdoc setangle!(wc::wordcloud, w, a::Number) = wc.params[:angles][index(wc, w)] = a +@doc setdoc +function setword!(wc::wordcloud, w, v::AbstractString) + m = getindsmap(wc) + @assert !(v in keys(m)) + wc.words[index(wc, w)] = v + m[v] = pop!(m, w) + v end -index(wc::wordcloud, i::Number) = i -getcolor(wc::wordcloud, w) = wc.params[:colors][index(wc, w)] -getangle(wc::wordcloud, w) = wc.params[:angles][index(wc, w)] -getweight(wc::wordcloud, w) = wc.weights[index(wc, w)] -setcolor!(wc::wordcloud, w, c) = wc.params[:colors][index(wc, w)] = parsecolor(c) -setangle!(wc::wordcloud, w, a::Number) = wc.params[:angles][index(wc, w)] = a -setweight!(wc::wordcloud, w, v::Number) = wc.weights[index(wc, w)] = v -getimage(wc::wordcloud, w) = wc.imgs[index(wc, w)] +@doc setdoc setweight!(wc::wordcloud, w, v::Number) = wc.weights[index(wc, w)] = v +@doc getdoc getimage(wc::wordcloud, w=:) = wc.imgs[index(wc, w)] +getmask(wc::wordcloud) = wc.mask -function getposition(wc::wordcloud, mask=:) +@doc getdoc * " Keyword argment `type` can be `getshift` or `getcenter`." +function getposition(wc::wordcloud, mask=:; type=getshift) msy, msx = getshift(wc.maskqtree) - pos = getshift.(wc.qtrees[mask]) + pos = type.(wc.qtrees[mask]) map(p->(p[2]-msx+1, p[1]-msy+1), pos) end -function getposition(wc::wordcloud, w::Union{Number, AbstractString}) +function getposition(wc::wordcloud, w::Union{Number, AbstractString}; type=getshift) msy, msx = getshift(wc.maskqtree) - y, x = getshift(wc.qtrees[index(wc, w)]) + y, x = type(wc.qtrees[index(wc, w)]) x-msx+1, y-msy+1 end -function setposition!(wc::wordcloud, w, x_y) + +@doc setdoc * " Keyword argment `type` can be `setshift!` or `setcenter!`." +function setposition!(wc::wordcloud, w, x_y; type=setshift!) x, y = x_y msy, msx = getshift(wc.maskqtree) - setshift!(wc.qtrees[index(wc, w)], (y-1+msy, x-1+msx)) + type(wc.qtrees[index(wc, w)], (y-1+msy, x-1+msx)) x_y end -function initword!(wc, w, sz=wc.weights[index(wc, w)]*wc.params[:scale]) + +"Initialize word's images and other resources with specified style" +function initword!(wc, w, sz=wc.weights[index(wc, w)]*wc.params[:scale]; + bgcolor=(0,0,0,0), border=wc.params[:border], font=wc.params[:font], minfontsize=wc.params[:minfontsize]) i = index(wc, w) params = wc.params img, mimg, tree = prepareword(wc.words[i], sz, params[:colors][i], params[:angles][i], params[:groundsize], - bgcolor=(0,0,0,0), border=params[:border], font=params[:font], minfontsize=params[:minfontsize]) + bgcolor=bgcolor, border=border, font=font, minfontsize=minfontsize) wc.imgs[i] = img wc.qtrees[i] = tree nothing @@ -213,7 +253,7 @@ function initword!(wc::wordcloud) fillingrate=params[:fillingrate], maxiter=5, error=0.03, font=params[:font], minfontsize=params[:minfontsize]) println("set fillingrate to $(params[:fillingrate]), with scale=$scale") params[:scale] = scale - initword!.(Ref(wc), 1:length(words)) + initword!.(wc, 1:length(words)) params[:state] = nameof(initword!) wc end @@ -227,12 +267,13 @@ function QTree.placement!(wc::wordcloud) wc end -function rescale!(wc::wordcloud, scale::Real) +"rescale!(wc::wordcloud, ratio::Real)\nRescale all words's size. set `ratio`<1 to shrink, set `ratio`>1 to expand." +function rescale!(wc::wordcloud, ratio::Real) qts = wc.qtrees - centers = QTree.center.(qts) - wc.params[:scale] = scale - initword!.(Ref(wc), 1:length(wc.words)) - QTree.setcenter!.(wc.qtrees, centers) + centers = getcenter.(qts) + wc.params[:scale] *= ratio + initword!.(wc, 1:length(wc.words)) + setcenter!.(wc.qtrees, centers) wc end @@ -276,8 +317,7 @@ function generate!(wc::wordcloud, args...; retry=3, krags...) for r in 1:retry # fr = feelingoccupied(wc.params[:mimgs])/wc.params[:groundoccupied] if r != 1 - sc = wc.params[:scale] * 0.95 - rescale!(wc, sc) + rescale!(wc, 0.95) end println("#$r. scale = $(wc.params[:scale])") ep, nc = train!(wc.qtrees, wc.maskqtree, args...; krags...) @@ -287,7 +327,7 @@ function generate!(wc::wordcloud, args...; retry=3, krags...) end end @show ep, nc - if false#nc == 0 + if nc == 0 wc.params[:state] = nameof(generate!) else #check colllist = first.(listcollision(wc.qtrees, wc.maskqtree)) @@ -317,6 +357,13 @@ function generate_animation!(wc::wordcloud, args...; outputdir="gifresult", over re end +""" +ignore some words as if they don't exist, then execute the function. +* ignore(fun, wc, ws::String) #ignore a word +* ignore(fun, wc, ws::Set{String}) #ignore all words in ws +* ignore(fun, wc, ws::Array{String}) #ignore all words in ws +* ignore(fun, wc::wordcloud, mask::AbstractArray{Bool}) #ignore words. length(mask)==length(wc.words) +""" function ignore(fun, wc::wordcloud, mask::AbstractArray{Bool}) mem = [wc.words, wc.weights, wc.imgs, wc.qtrees, wc.params[:colors], wc.params[:angles], wc.params[:indsmap]] @@ -342,7 +389,14 @@ function ignore(fun, wc::wordcloud, mask::AbstractArray{Bool}) end r end - + +""" +pin some words as if they were part of the background, then execute the function. +* pin(fun, wc, ws::String) #pin a word +* pin(fun, wc, ws::Set{String}) #pin all words in ws +* pin(fun, wc, ws::Array{String}) #pin all words in ws +* pin(fun, wc::wordcloud, mask::AbstractArray{Bool}) #pin words. length(mask)==length(wc.words) +""" function pin(fun, wc::wordcloud, mask::AbstractArray{Bool}) maskqtree = wc.maskqtree wcmask = wc.mask diff --git a/src/nlp.jl b/src/nlp.jl index bd03c8d..0e03e6c 100644 --- a/src/nlp.jl +++ b/src/nlp.jl @@ -15,7 +15,7 @@ function countwords(words::AbstractVector{<:AbstractString}; counter=Dict{String end countwords(text::AbstractString; regexp=r"\w[\w']+", kargs...) = countwords(splitwords(text, regexp); kargs...) - +"count words in text. And use `regexp` to split." function countwords(textfile::IO; counter=Dict{String,Int}(), kargs...) for l in eachline(textfile) countwords(l;counter=counter, kargs...) @@ -29,13 +29,25 @@ end # end # counter # end - -function process(counter::Dict{String,<:Number}; +""" +Process the text, filter the words, and adjust the weights. return processed words vector and weights vector. +## Positional Arguments +* text: string, a vector of words, or a opend file(IO) +* Or, a counter::Dict{<:AbstractString, <:Number} +## Optional Keyword Arguments +* stopwords: a words Set +* minlength, maxlength: min and max length of a word to be included +* minfrequency: minimum frequency of a word to be included +* maxnum: maximum number of words +* minweight, maxweight: within 0 ~ 1, set to adjust extreme weight +""" +function process(counter::Dict{<:AbstractString, <:Number}; stopwords=stopwords_en, minlength=2, maxlength=30, minfrequency=0, maxnum=500, minweight=1/maxnum, maxweight=minweight*20) + stopwords = Set(stopwords) for (w, c) in counter if (c < minfrequency || length(w) < minlength || length(w) > maxlength || lowercase(w) in stopwords) delete!(counter, w) diff --git a/src/qtree.jl b/src/qtree.jl index dad340d..b93d3ec 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, - collision, collision_bfs, collision_bfs_rand, listcollision, - findroom, levelnum, outofbounds, kernelsize, placement!, decode, placement! + shift!, setrshift!, setcshift!, setshift!, getshift, getcenter, setcenter!, + collision, collision_bfs, collision_bfs_rand, listcollision, + findroom, levelnum, outofbounds, kernelsize, placement!, decode using Random using Combinatorics @@ -219,12 +219,11 @@ setshift!(t::ShiftedQtree, l::Integer, st::Tuple{Integer,Integer}) = setshift!(t 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]) -center(t::ShiftedQtree) = getshift(t) .+ kernelsize(t) .÷ 2 +getcenter(t::ShiftedQtree) = getshift(t) .+ kernelsize(t) .÷ 2 callefttop(t::ShiftedQtree, center) = center .- kernelsize(t) .÷ 2 setcenter!(t::ShiftedQtree, center) = setshift!(t, callefttop(t, center)) - function inbounds(bgqt::ShiftedQtree, qt::ShiftedQtree) - inbounds(bgqt[1], center(qt)...) + inbounds(bgqt[1], getcenter(qt)...) end function outofbounds(bgqt::ShiftedQtree, qts) [i for (i,t) in enumerate(qts) if !inbounds(bgqt, t)] diff --git a/src/utils.jl b/src/utils.jl index ee8cbfe..14c7115 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -4,4 +4,26 @@ function imageof(layer::AbstractMatrix{UInt8}) Gray.(decode(layer)) end -bitor(l) = reduce((a,b)->a.|b, l) \ No newline at end of file +bitor(l) = reduce((a,b)->a.|b, l) + +FULL = WordCloud.QTree.FULL +EMPTY = WordCloud.QTree.EMPTY +HALF = WordCloud.QTree.HALF +function testqtree(qt) + for l in 2:WordCloud.levelnum(qt) + for i in 1:size(qt[l], 1) + for j in 1:size(qt[l], 2) + c = [qt[l-1, 2i, 2j], qt[l-1, 2i-1, 2j], qt[l-1, 2i, 2j-1], qt[l-1, 2i-1, 2j-1]] + if qt[l, i, j] == FULL + @assert all(c .== FULL) (qt[l, i, j], (l, i, j)) + elseif qt[l, i, j] == EMPTY + @assert all(c .== EMPTY) (qt[l, i, j], (l, i, j)) + elseif qt[l, i, j] == HALF + @assert !(all(c .== FULL) || all(c .== EMPTY)) (qt[l, i, j], (l, i, j)) + else + error(qt[l, i, j], (l, i, j)) + end + end + end + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 4883426..e28b716 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,7 +20,7 @@ end wc = wordcloud(words, weights, fillingrate=0.6) paint(wc) generate!(wc) - paint(wc::wordcloud, "test.jpg") + paint(wc::wordcloud, "test.jpg", background=outline(wc.mask, color=(1, 0, 0.2, 0.7), linewidth=2)) @test isempty(WordCloud.outofbounds(wc.maskqtree, wc.qtrees)) clq = WordCloud.QTree.listcollision_qtree(wc.qtrees, wc.maskqtree) @@ -32,10 +32,17 @@ end maskimg=shape(box, 20, 15, 0, color=0.15), fillingrate=0.5, transparentcolor=(1,1,1,0)) #String & small mask placement!(wc) wc = wordcloud( - process(open("../res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"]), + process(open("../res/alice.txt"), stopwords=WordCloud.stopwords_en ∪ ["said"], maxnum=300), mask = loadmask("../res/alice_mask.png", color="#faeef8", backgroundcolor=0.97), colors = (WordCloud.colorschemes[:Set1_5].colors..., ), angles = (0, 90), fillingrate = 0.6); + rescale!(wc, 1.23) + pin(wc, ["little", "know"]) do + @test length(wc.words)==298 + setposition!(wc, "time", (0,0), type=setcenter!) + end + @test WordCloud.QTree.kernelsize(wc.qtrees[WordCloud.index(wc, "time")]) == size(getimage(wc, "time")) + @test .-reverse(size(getimage(wc, "time"))) .÷ 2 == getposition(wc, "time") end diff --git a/test/test_qtree.jl b/test/test_qtree.jl index b2381db..07c247f 100644 --- a/test/test_qtree.jl +++ b/test/test_qtree.jl @@ -1,30 +1,10 @@ -FULL = WordCloud.QTree.FULL -EMPTY = WordCloud.QTree.EMPTY -HALF = WordCloud.QTree.HALF -function testqtree(qt) - for l in 2:WordCloud.levelnum(qt) - for i in 1:size(qt[l], 1) - for j in 1:size(qt[l], 2) - c = [qt[l-1, 2i, 2j], qt[l-1, 2i-1, 2j], qt[l-1, 2i, 2j-1], qt[l-1, 2i-1, 2j-1]] - if qt[l, i, j] == FULL - @test all(c .== FULL) - elseif qt[l, i, j] == EMPTY - @test all(c .== EMPTY) - elseif qt[l, i, j] == HALF - @test !(all(c .== FULL) || all(c .== EMPTY)) - else - error(qt[l, i, j], (l, i, j)) - end - end - end - end -end +testqtree = WordCloud.testqtree @testset "qtree.jl" begin # Write your tests here. qt = WordCloud.ShiftedQtree(rand((0,0,1), rand(50:300), rand(50:300)))|>WordCloud.buildqtree! - @test qt[1][-10,-15] == EMPTY - @test_throws BoundsError qt[1][-10,-15] = EMPTY + @test qt[1][-10,-15] == WordCloud.EMPTY + @test_throws BoundsError qt[1][-10,-15] = WordCloud.EMPTY qt2 = WordCloud.ShiftedQtree(rand((0,0,1), size(qt[1])))|>WordCloud.buildqtree! testqtree(qt) WordCloud.shift!(qt, 3, 2, 5)