From 86ae87499616b7cdb78df1a7bdcd598e3e5c595a Mon Sep 17 00:00:00 2001 From: Ashwani Rathee Date: Mon, 26 Dec 2022 01:19:10 +0545 Subject: [PATCH 1/3] Base for octree quantization --- Project.toml | 2 + src/ColorQuantization.jl | 8 +- src/octree.jl | 154 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/octree.jl diff --git a/Project.toml b/Project.toml index 232241b..6cc7c8c 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,8 @@ Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e" LazyModules = "8cdb02fc-e678-4876-92c5-9defec4f444e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +RegionTrees = "dee08c22-ab7f-5625-9660-a9af2021b33f" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] Clustering = "0.14.3" diff --git a/src/ColorQuantization.jl b/src/ColorQuantization.jl index 3c59bfd..3896568 100644 --- a/src/ColorQuantization.jl +++ b/src/ColorQuantization.jl @@ -1,10 +1,13 @@ module ColorQuantization using Colors -using ImageBase: FixedPoint, floattype, FixedPointNumbers.rawtype +using ImageBase: FixedPoint, floattype, FixedPointNumbers.rawtype, FixedPointNumbers.N0f8 using ImageBase: channelview, colorview, restrict using Random: AbstractRNG, GLOBAL_RNG using LazyModules: @lazy +using RegionTrees +using StaticArrays + #! format: off @lazy import Clustering = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5" #! format: on @@ -15,8 +18,9 @@ include("api.jl") include("utils.jl") include("uniform.jl") include("clustering.jl") # lazily loaded +include("octree.jl") export AbstractColorQuantizer, quantize -export UniformQuantization, KMeansQuantization +export UniformQuantization, KMeansQuantization, OctreeQuantization end diff --git a/src/octree.jl b/src/octree.jl new file mode 100644 index 0000000..06a82aa --- /dev/null +++ b/src/octree.jl @@ -0,0 +1,154 @@ + +struct OctreeQuantization <: AbstractColorQuantizer + numcolors::Int + function OctreeQuantization( + colorspace::Type{<:Colorant}, + numcolors::Int = 256; + kwargs..., + ) + colorspace == RGB{N0f8} || error("Octree Algorithm only supports RGB colorspace") + return new(numcolors) + end +end + +const OCTREE_DEFAULT_COLORSPACE = RGB{N0f8} +# Color to RGB +# constructor for struct +function OctreeQuantization(numcolors::Int = 256; kwargs...) + return OctreeQuantization(OCTREE_DEFAULT_COLORSPACE, numcolors; kwargs...) +end + +function (alg::OctreeQuantization)(img::AbstractArray) + return octreequantisation!(img; numcolors = alg.numcolors) +end + +function octreequantisation!(img; numcolors = 256, precheck::Bool = false) + # ensure the img is in RGB colorspace + if (eltype(img) != RGB{N0f8}) + error("Octree Algorithm requires img to be in RGB colorspace") + end + + # checks if image has more colors than in numcolors + if precheck == true + unumcolors = length(unique(img)) + # @show unumcolors + if unumcolors <= numcolors + @debug "Image has $unumcolors unique colors" + return unique(img) + end + end + + # step 1: creating the octree + root = Cell( + SVector(0.0, 0.0, 0.0), + SVector(1.0, 1.0, 1.0), + ["root", 0, [], RGB{N0f8}.(0.0, 0.0, 0.0), 0], + ) + cases = map( + p -> [bitstring(UInt8(p))[6:end], 0, Vector{Int}([]), RGB{N0f8}.(0.0, 0.0, 0.0), 1], + 1:8, + ) + split!(root, cases) + inds = collect(1:length(img)) + + function putin(root, in) + r, g, b = map(p -> bitstring(UInt8(p * 255)), channelview([img[in]])) + rgb = r[1] * g[1] * b[1] + # finding the entry to the tree + ind = 0 + for i = 1:8 + if (root.children[i].data[1] == rgb) + root.children[i].data[2] += 1 + ind = i + break + end + end + curr = root.children[ind] + + for i = 2:8 + cases = map( + p -> [ + bitstring(UInt8(p))[6:end], + 0, + Vector{Int}([]), + RGB{N0f8}.(0.0, 0.0, 0.0), + i, + ], + 1:8, + ) + rgb = r[i] * g[i] * b[i] + if (isleaf(curr) == true && i <= 8) + split!(curr, cases) + end + if (i == 8) + for j = 1:8 + if (curr.children[j].data[1] == rgb) + curr = curr.children[j] + curr.data[2] += 1 + push!(curr.data[3], in) + curr.data[4] = img[in] + return + end + end + end + + # handle 1:7 cases for rgb to handle first seven bits + for j = 1:8 + if (curr.children[j].data[1] == rgb) + curr.children[j].data[2] += 1 + curr = curr.children[j] + break + end + end + end + end + + # build the tree + for i in inds + root.data[2] += 1 + putin(root, i) + end + + # step 2: reducing tree to a certain number of colors + # there is scope for improvements in allleaves as it's found again n again + leafs = [p for p in allleaves(root)] + filter!(p -> !iszero(p.data[2]), leafs) + tobe_reduced = leafs[1] + + while (length(leafs) > numcolors) + parents = unique([parent(p) for p in leafs]) + parents = sort(parents; by = c -> c.data[2]) + tobe_reduced = parents[1] + # @show tobe_reduced.data + + for i = 1:8 + append!(tobe_reduced.data[3], tobe_reduced.children[i].data[3]) + tobe_reduced.data[4] += + tobe_reduced.children[i].data[4] * tobe_reduced.children[i].data[2] + end + tobe_reduced.data[4] /= tobe_reduced.data[2] + tobe_reduced.children = nothing + + # we don't want to do this again n again + leafs = [p for p in allleaves(root)] + filter!(p -> !iszero(p.data[2]), leafs) + end + + # step 3: palette formation and quantisation now + da = [p.data for p in leafs] + for i in da + for j in i[3] + img[j] = i[4] + end + end + + colors = [p[4] for p in da] + return colors +end + +function octreequantisation(img; kwargs...) + img_copy = deepcopy(img) + palette = octreequantisation!(img_copy; kwargs...) + return img_copy, palette +end + From 31ed5e9ea4099dea1d0d9832d67b41900c03537e Mon Sep 17 00:00:00 2001 From: ashwani rathee Date: Tue, 25 Apr 2023 19:40:31 +0530 Subject: [PATCH 2/3] relatively better but still awfully slow --- Project.toml | 2 + src/octree.jl | 124 ++++++++++++++++---------------------------------- 2 files changed, 41 insertions(+), 85 deletions(-) diff --git a/Project.toml b/Project.toml index 6cc7c8c..961a944 100644 --- a/Project.toml +++ b/Project.toml @@ -17,4 +17,6 @@ Clustering = "0.14.3" Colors = "0.12" ImageBase = "0.1" LazyModules = "0.3" +RegionTrees = "0.3.2" +StaticArrays = "0.12, 1" julia = "1.6" diff --git a/src/octree.jl b/src/octree.jl index 06a82aa..2237cb8 100644 --- a/src/octree.jl +++ b/src/octree.jl @@ -2,24 +2,15 @@ struct OctreeQuantization <: AbstractColorQuantizer numcolors::Int function OctreeQuantization( - colorspace::Type{<:Colorant}, - numcolors::Int = 256; - kwargs..., + numcolors::Int=256; + kwargs... ) - colorspace == RGB{N0f8} || error("Octree Algorithm only supports RGB colorspace") return new(numcolors) end end -const OCTREE_DEFAULT_COLORSPACE = RGB{N0f8} -# Color to RGB -# constructor for struct -function OctreeQuantization(numcolors::Int = 256; kwargs...) - return OctreeQuantization(OCTREE_DEFAULT_COLORSPACE, numcolors; kwargs...) -end - function (alg::OctreeQuantization)(img::AbstractArray) - return octreequantisation!(img; numcolors = alg.numcolors) + return octreequantisation!(img; numcolors=alg.numcolors) end function octreequantisation!(img; numcolors = 256, precheck::Bool = false) @@ -39,110 +30,73 @@ function octreequantisation!(img; numcolors = 256, precheck::Bool = false) end # step 1: creating the octree - root = Cell( - SVector(0.0, 0.0, 0.0), - SVector(1.0, 1.0, 1.0), - ["root", 0, [], RGB{N0f8}.(0.0, 0.0, 0.0), 0], - ) - cases = map( - p -> [bitstring(UInt8(p))[6:end], 0, Vector{Int}([]), RGB{N0f8}.(0.0, 0.0, 0.0), 1], - 1:8, - ) + root = Cell(SVector(0.0, 0.0, 0.0), SVector(1.0, 1.0, 1.0), [0, [], RGB{N0f8}.(0.0, 0.0, 0.0), 0]) + cases = map(p -> [0, Vector{Int}([]), RGB{N0f8}.(0.0, 0.0, 0.0), 1], 1:8) split!(root, cases) inds = collect(1:length(img)) - function putin(root, in) - r, g, b = map(p -> bitstring(UInt8(p * 255)), channelview([img[in]])) - rgb = r[1] * g[1] * b[1] - # finding the entry to the tree - ind = 0 - for i = 1:8 - if (root.children[i].data[1] == rgb) - root.children[i].data[2] += 1 - ind = i - break - end - end - curr = root.children[ind] - - for i = 2:8 - cases = map( - p -> [ - bitstring(UInt8(p))[6:end], - 0, - Vector{Int}([]), - RGB{N0f8}.(0.0, 0.0, 0.0), - i, - ], - 1:8, - ) - rgb = r[i] * g[i] * b[i] - if (isleaf(curr) == true && i <= 8) - split!(curr, cases) - end - if (i == 8) - for j = 1:8 - if (curr.children[j].data[1] == rgb) - curr = curr.children[j] - curr.data[2] += 1 - push!(curr.data[3], in) - curr.data[4] = img[in] - return - end - end - end - - # handle 1:7 cases for rgb to handle first seven bits - for j = 1:8 - if (curr.children[j].data[1] == rgb) - curr.children[j].data[2] += 1 - curr = curr.children[j] - break - end + function putin(c::Cell, i::Int, level::Int) + if (level == 8) + push!(c.data[2], i) + c.data[3] = img[i] + return + else + if (isleaf(c) == true && level <= 8) + cadata = map(p -> [0, Vector{Int}([]), RGB{N0f8}.(0.0, 0.0, 0.0), level + 1], 1:8) + split!(c, cadata) end + r, g, b = map(p -> bitstring(UInt8(p * 255)), channelview([img[i]])) + rgb = r[level] * g[level] * b[level] + rgb = parse(Int, rgb, base=2) + 1 + c.children[rgb].data[1] += 1 + putin(c.children[rgb], i, level + 1) end end + level = 1 + root.data[1] = length(inds) + # build the tree for i in inds - root.data[2] += 1 - putin(root, i) + r, g, b = map(p -> bitstring(UInt8(p * 255)), channelview([img[i]])) + rgb = r[level] * g[level] * b[level] + rgb = parse(Int, rgb, base=2) + 1 + root.children[rgb].data[1] += 1 + putin(root.children[rgb], i, level) end # step 2: reducing tree to a certain number of colors # there is scope for improvements in allleaves as it's found again n again leafs = [p for p in allleaves(root)] - filter!(p -> !iszero(p.data[2]), leafs) + filter!(p -> !iszero(p.data[1]), leafs) tobe_reduced = leafs[1] while (length(leafs) > numcolors) parents = unique([parent(p) for p in leafs]) - parents = sort(parents; by = c -> c.data[2]) + parents = sort(parents; by = c -> c.data[1]) tobe_reduced = parents[1] - # @show tobe_reduced.data for i = 1:8 - append!(tobe_reduced.data[3], tobe_reduced.children[i].data[3]) - tobe_reduced.data[4] += - tobe_reduced.children[i].data[4] * tobe_reduced.children[i].data[2] + append!(tobe_reduced.data[2], tobe_reduced.children[i].data[2]) + tobe_reduced.data[3] += + tobe_reduced.children[i].data[3] * tobe_reduced.children[i].data[1] end - tobe_reduced.data[4] /= tobe_reduced.data[2] - tobe_reduced.children = nothing - # we don't want to do this again n again - leafs = [p for p in allleaves(root)] - filter!(p -> !iszero(p.data[2]), leafs) + tobe_reduced.data[3] /= tobe_reduced.data[1] + filter!(!in(tobe_reduced.children),leafs) + push!(leafs, tobe_reduced) + tobe_reduced.children = nothing end # step 3: palette formation and quantisation now da = [p.data for p in leafs] for i in da - for j in i[3] - img[j] = i[4] + for j in i[2] + img[j] = i[3] end end - colors = [p[4] for p in da] + colors = [p[3] for p in da] return colors end From d8df7543c758e586d5378c2b34c9d096a0f5f91e Mon Sep 17 00:00:00 2001 From: ashwani rathee Date: Tue, 25 Apr 2023 19:54:38 +0530 Subject: [PATCH 3/3] The zs ;-; --- src/octree.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/octree.jl b/src/octree.jl index 2237cb8..695b22e 100644 --- a/src/octree.jl +++ b/src/octree.jl @@ -1,19 +1,16 @@ struct OctreeQuantization <: AbstractColorQuantizer numcolors::Int - function OctreeQuantization( - numcolors::Int=256; - kwargs... - ) + function OctreeQuantization(numcolors::Int=256; kwargs...) return new(numcolors) end end function (alg::OctreeQuantization)(img::AbstractArray) - return octreequantisation!(img; numcolors=alg.numcolors) + return octreequantization!(img; numcolors=alg.numcolors) end -function octreequantisation!(img; numcolors = 256, precheck::Bool = false) +function octreequantization!(img; numcolors = 256, precheck::Bool = false) # ensure the img is in RGB colorspace if (eltype(img) != RGB{N0f8}) error("Octree Algorithm requires img to be in RGB colorspace") @@ -100,9 +97,9 @@ function octreequantisation!(img; numcolors = 256, precheck::Bool = false) return colors end -function octreequantisation(img; kwargs...) +function octreequantization(img; kwargs...) img_copy = deepcopy(img) - palette = octreequantisation!(img_copy; kwargs...) + palette = octreequantization!(img_copy; kwargs...) return img_copy, palette end