Skip to content
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

[WIP] rework SuperPixel and SuperPixelImage with Array interface #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ authors = ["Johnny Chen <[email protected]>"]
version = "0.0.1"

[deps]
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4"
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
ImageDraw = "4381153b-2b60-58ae-a1ba-fd683676385f"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
ImageCore = ">=0.8.5"
ColorVectorSpace = "0.7, 0.8"
ImageCore = "0.8.5"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Suppressor"]
10 changes: 4 additions & 6 deletions src/SuperPixels.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
module SuperPixels

using ImageCore, ColorVectorSpace
using Base: tail
using Statistics
import ColorTypes: color_type, color
import Base: position
using FillArrays
using ImageCore, ColorVectorSpace

include("types.jl")
include("synthesize.jl")

export
SuperPixel, color, position,
# utils
imsize,
SuperPixel,
# algorithms
synthesize, Raw, Average

Expand Down
117 changes: 80 additions & 37 deletions src/synthesize.jl
Original file line number Diff line number Diff line change
@@ -1,56 +1,99 @@
abstract type AbstractSynthesizeMethod end
# An intermediate helper class for synthesize
struct SuperPixelImage{C, N, AT<:AbstractArray{<:SuperPixel}} <: AbstractArray{C, N}
tiles::AT

"""
Raw <: AbstractSynthesizeMethod
axes::NTuple{N, UnitRange{Int}}
function SuperPixelImage(tiles::SPI) where SPI<:AbstractArray{<:SuperPixel}
C = eltype(eltype(SPI))
N = ndims(eltype(SPI))
new{C, N, SPI}(tiles, _spi_axes(axes.(tiles)...))
end
end
SuperPixelImage(img::SuperPixelImage) = img

Pixel values inside SuperPixel is not changed.
"""
struct Raw <: AbstractSynthesizeMethod end
"""
Mean <: AbstractSynthesizeMethod
@inline _spi_axes(indices...) = __spi_axes(first(indices), tail(indices))
@inline function __spi_axes(R1, indices::Tuple)
# TODO: accelerate this?
R2 = first(indices)
R = ntuple(length(R1)) do i
r1, r2 = R1[i], R2[i]
min(first(r1), first(r2)):max(last(r1), last(r2))
end
__spi_axes(R, tail(indices))
end
@inline __spi_axes(R, indices::Tuple{}) = R

Pixel values inside SuperPixel is averaged.
"""
struct Average <: AbstractSynthesizeMethod end
@inline Base.axes(spi::SuperPixelImage) = spi.axes
@inline Base.size(spi::SuperPixelImage) = length.(spi.axes)

### synthesize

abstract type AbstractSynthesizeMethod end

"""
synthesize([CT], img, [method=Raw()])

Collect pixels in `img` and returns an image of type `Array{CT}`.

Argument `method` controls how pixels are collected, supported methods
are [`Raw`](@ref) and [`Average`](@ref). The default value is `Raw`.
are [`Raw`](@ref) and [`Average`](@ref). The default value is `Raw()`.
"""
function synthesize(img::AbstractArray, args...; kwargs...)
img = SuperPixelImage(img)
synthesize(eltype(img), img, args...; kwargs...)
end
synthesize(::Type{T}, img::AbstractArray, args...; kwargs...) where T =
synthesize(T, SuperPixelImage(img), args...; kwargs...)
synthesize(::Type{T}, img::SuperPixelImage, method=Raw(img), args...; kwargs...) where T =
synthesize(T, img, method, args...; kwargs...)

!!! note
Overlaps between superpixels are averaged. Empty pixels are filled
by zeros.
"""
synthesize(img::AbstractArray{<:SuperPixel}, method = Raw()) =
synthesize(color_type(img), img, method)
synthesize(::Type{T}, img::AbstractArray{<:SuperPixel}, method = Raw()) where T =
synthesize(T, img, method)

function synthesize(::Type{CT},
img::AbstractArray{<:SuperPixel},
::Raw) where CT <: Union{AbstractRGB, AbstractGray}

out = zeros(CT, imsize(img))
count = zeros(Int, imsize(img)) # overlap count
for SP in img
isempty(SP) && continue
R = position(SP)
out[R] .+= color(SP)
Raw <: AbstractSynthesizeMethod
Raw(fillvalue=0)

Pixel values inside SuperPixel is not changed.

Overlaps between superpixels are averaged. Missing pixels are filled by `fillvalue`.
"""
struct Raw{C<:Union{Number, Colorant}} <: AbstractSynthesizeMethod
fillvalue::C
end
Raw() = Raw(0)
Raw(img::SuperPixelImage) = Raw(zero(eltype(img)))

function synthesize(::Type{T}, img::SuperPixelImage, S::Raw) where T
out = zeros(T, size(img))
count = zeros(Int, size(img)) # overlap counts

for sp in img.tiles
isempty(sp) && continue
R = CartesianIndices(sp.indices)
out[R] .+= @view sp.values[R]
count[R] .+= 1
end

R = count.==0
out[R] .= S.fillvalue
count[R] .= 1
return out./count
end

function synthesize(::Type{CT},
img::AbstractArray{<:SuperPixel},
::Average) where CT <: Union{AbstractRGB, AbstractGray}
averaged_img = map(img) do SP
SuperPixel(fill(mean(SP.color), size(SP.color)),
position(SP))
"""
Mean <: AbstractSynthesizeMethod
Mean(fillvalue=0)

Pixel values inside SuperPixel is averaged.

Overlaps between superpixels are averaged. Missing pixels are filled by `fillvalue`.
"""
struct Average <: AbstractSynthesizeMethod
fillvalue
end
Average() = Average(0)

function synthesize(::Type{T}, img::SuperPixelImage, S::Average) where T
averaged_img = map(img.tiles) do sp
SuperPixel(Fill(mean(sp), axes(sp)), axes(sp))
end
return synthesize(CT, averaged_img, Raw())
return synthesize(T, averaged_img, Raw(S.fillvalue))
end
79 changes: 25 additions & 54 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@ A super pixel is a collection of pixels with their position information.

# Fields

* `color::AbstractArray{<:Colorant}` stores its pixel/color information.
* `position::CartesianIndices` stores its position information.
* `values::AbstractArray{<:Colorant}` stores its pixel/color information.
* `indices::CartesianIndices` stores its position information.

Relative order in `color` and `position` is important; `color[idx]` is the
image pixel at position `position[idx]`.

!!! tip

There's a helper for each field. For example, `color(sp)` is equivalent to
`sp.color`. This is useful to enable broadcasting such as `position.(img)`
where `img` is a `SuperPixel` array.
Relative order in `values` and `indices` is important; `values[idx]` is the
image pixel at position `indices[idx]`.

# Examples

Expand Down Expand Up @@ -54,55 +48,32 @@ synthesize(img_sp) # composite superpixels into a complete image
synthesize(img_sp, Average()) # each superpixel is averaged first
```
"""
struct SuperPixel{T<:Colorant, N, AT<:AbstractArray{T}}
color::AT
position
end
struct SuperPixel{C, N, AT<:AbstractArray{C, N}, R} <: AbstractArray{C, N}
values::AT
indices::R

function SuperPixel(
color::AbstractArray{T},
position::AbstractArray{CartesianIndex{N}}) where {T<:Colorant, N}

color = length(color) == length(position) ? color : color[position]
SuperPixel{T, N, typeof(color)}(color, position)
axes::NTuple{N, UnitRange{Int}}
function SuperPixel(values::AbstractArray{C, N}, indices) where {C, N}
new{C, N, typeof(values), typeof(indices)}(values, indices, _sp_axes(indices))
end
end

SuperPixel(color::AbstractArray{<:Colorant}, pos::Tuple) =
SuperPixel(color, CartesianIndices(pos))

SuperPixel(pixels::AbstractArray{<:Number}, pos) =
SuperPixel(Gray.(pixels), pos)

color(sp::SuperPixel) = sp.color
position(sp::SuperPixel) = sp.position

# support usage such as RGB.(img_sp)
function Base.convert(::Type{C}, sp::SuperPixel) where {C<:Colorant}
CT = ccolor(C, eltype(sp.color))
src = sp.color
dest = similar(src, CT)
@inbounds for idx in CartesianIndices(src)
dest[idx] = ColorTypes.cconvert(ccolor(C, CT), src[idx])
@inline _sp_axes(indices::CartesianIndices) = indices.indices
@inline _sp_axes(indices::Tuple) = indices
@inline function _sp_axes(indices::AbstractArray)
N = length(view(indices, 1))
ntuple(N) do i
r = ntuple(length(indices)) do j
@inbounds indices[j][i]
end
UnitRange(extrema(r)...)
end
return SuperPixel(dest, sp.position)
end

Base.isempty(sp::SuperPixel) = isempty(color(sp))
Base.:(==)(x::SuperPixel, y::SuperPixel) = x.position == y.position && x.color == y.color

color_type(sp::SuperPixel{CT}) where CT <: Colorant = CT
color_type(img::AbstractArray{<:SuperPixel}) = color_type(eltype(img))
color_type(::Type{<:SuperPixel{CT}}) where CT <: Colorant = CT
Base.axes(sp::SuperPixel) = sp.axes
Base.size(sp::SuperPixel) = length.(axes(sp))

"""
imsize(img)

Returns the size of potential image represented by [`SuperPixel`](@ref).
"""
imsize(img::AbstractArray{<:SuperPixel}) = _size(Iterators.flatten(position.(img)))
imsize(img::SuperPixel) = _size(position(img))
imsize(img::AbstractArray) = size(img)
function _size(R)
I_first, I_last = extrema(R)
return I_last.I .- I_first.I .+ (1, 1)
Base.@propagate_inbounds function Base.getindex(sp::SuperPixel, ind...)
# TODO: check whether ind ∈ sp.indices
getindex(sp.values, ind...)
end
8 changes: 1 addition & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
using ImageCore
using Test, Suppressor

refambs = @suppress_out detect_ambiguities(ImageCore, Base)
using SuperPixels
ambs = @suppress_out detect_ambiguities(ImageCore, Base, SuperPixels)

@testset "SuperPixels.jl" begin
# check if SuperPixels.jl introduces new ambiguities
@test isempty(setdiff(ambs, refambs))

include("types.jl")
# include("types.jl")
include("synthesize.jl")
end

Expand Down