Skip to content

Octree quantization #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -9,10 +9,14 @@ 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"
Colors = "0.12"
ImageBase = "0.1"
LazyModules = "0.3"
RegionTrees = "0.3.2"
StaticArrays = "0.12, 1"
julia = "1.6"
8 changes: 6 additions & 2 deletions src/ColorQuantization.jl
Original file line number Diff line number Diff line change
@@ -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
105 changes: 105 additions & 0 deletions src/octree.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

struct OctreeQuantization <: AbstractColorQuantizer
numcolors::Int
function OctreeQuantization(numcolors::Int=256; kwargs...)
return new(numcolors)
end
end

function (alg::OctreeQuantization)(img::AbstractArray)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried running

using ColorQuantization, TestImages

img = testimage("fabio")
img = RGB{N0f8}.(img)
alg = OctreeQuantization(64)
alg(img)

however, the algorithm doesn't terminate after waiting several minutes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is fabio having high number of colors combined with how I prune the tree, lines using allleaves(root) is where trouble is

julia> unique(img)
37499-element Array{RGB{N0f8},1} with eltype RGB{N0f8}:
 RGB{N0f8}(0.541,0.506,0.51)
 RGB{N0f8}(0.459,0.337,0.325)
 RGB{N0f8}(0.51,0.341,0.306)
 RGB{N0f8}(0.455,0.341,0.278)
 RGB{N0f8}(0.435,0.349,0.306)
 ⋮
 RGB{N0f8}(0.898,0.569,0.424)
 RGB{N0f8}(0.906,0.58,0.416)
 RGB{N0f8}(0.925,0.596,0.424)
 RGB{N0f8}(0.89,0.561,0.388)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually horrifyingly slow ;-;

return octreequantization!(img; numcolors=alg.numcolors)
end

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")
end
Comment on lines +15 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe octreequantization could cast input images to RGB{N0f8} for the user instead of deepcopying them. This way, this error could be avoided automatically.

Copy link
Member Author

@ashwanirathee ashwanirathee Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could surely but then we are quantising a different image entirely in a different colorspace, we def could avoid deepcopying if we are not gonna accept it if it not right type


# 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), [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(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
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[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[1])
tobe_reduced = parents[1]

for i = 1:8
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[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[2]
img[j] = i[3]
end
end

colors = [p[3] for p in da]
return colors
end

function octreequantization(img; kwargs...)
img_copy = deepcopy(img)
palette = octreequantization!(img_copy; kwargs...)
return img_copy, palette
end