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

Use Julia 1.6's new reinterpretarray machinery #145

Merged
merged 2 commits into from
Oct 8, 2020
Merged
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
6 changes: 5 additions & 1 deletion src/ImageCore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ ColorA{N,C,T} = ColorAlpha{C,T,N}
const NonparametricColors = Union{RGB24,ARGB32,Gray24,AGray32}
Color1Array{C<:Color1,N} = AbstractArray{C,N}
# Type that arises from reshape(reinterpret(To, A), sz):
const RRArray{To,From,N,M,P} = Base.ReshapedArray{To,N,Base.ReinterpretArray{To,M,From,P}}
if VERSION >= v"1.6.0-DEV.1083"
const RRArray{To,From,M,P} = Base.ReinterpretArray{To,M,From,P,true}
else
const RRArray{To,From,N,M,P} = Base.ReshapedArray{To,N,Base.ReinterpretArray{To,M,From,P}}
end
const RGArray = Union{Base.ReinterpretArray{<:AbstractGray,M,<:Number,P}, Base.ReinterpretArray{<:Number,M,<:AbstractGray,P}} where {M,P}

# delibrately not export these constants to enable extensibility for downstream packages
Expand Down
43 changes: 32 additions & 11 deletions src/colorchannels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ dimorder(::Type{<:AlphaColor{<:Color3,T,N}}) where {T,N} = ColorChanPerm((4, 1,
const ColorChanPermIndexType{NC} = Tuple{<:ColorChanPerm,Vararg{<:Base.Slice,NC}}
const ColorChanPermSubArray{T,N,P,I<:ColorChanPermIndexType,L} =
SubArray{T,N,P,I,L}
const RRPermArray{To,From,N,M,P<:ColorChanPermSubArray} =
RRArray{To,From,N,M,P}
if VERSION >= v"1.6.0-DEV.1083"
const RRPermArray{To,From,M,P<:ColorChanPermSubArray} =
RRArray{To,From,M,P}
else
const RRPermArray{To,From,N,M,P<:ColorChanPermSubArray} =
RRArray{To,From,N,M,P}
end

# This type exists solely to set multiple values in the color channel axis
struct NVector{T,N} <: AbstractVector{T}
Expand Down Expand Up @@ -62,8 +67,13 @@ A = channelview(img) # a 3×10×10 array
See also: [`colorview`](@ref)
"""
channelview(A::AbstractArray{T}) where {T<:Number} = A
channelview(A::RRPermArray{<:Colorant,<:Number}) = parent(parent(parent(A)))
channelview(A::RRArray{<:Colorant,<:Number}) = parent(parent(A))
if VERSION >= v"1.6.0-DEV.1083"
channelview(A::RRArray{<:Colorant,<:Number}) = parent(A)
channelview(A::RRPermArray{<:Colorant,<:Number}) = parent(parent(A))
else
channelview(A::RRArray{<:Colorant,<:Number}) = parent(parent(A))
channelview(A::RRPermArray{<:Colorant,<:Number}) = parent(parent(parent(A)))
end
channelview(A::Base.ReinterpretArray{<:AbstractGray,M,<:Number}) where M = parent(A)
channelview(A::AbstractArray{RGB{T}}) where {T} = reinterpretc(T, A)
function channelview(A::AbstractArray{C}) where {C<:AbstractRGB}
Expand Down Expand Up @@ -108,14 +118,25 @@ _ccolorview(::Type{C}, A::RRPermArray{T,C}) where {C<:Colorant,T<:Number} =
parent(parent(parent(A)))
_ccolorview(::Type{C}, A::RRArray{T,C}) where {C<:Colorant,T<:Number} =
parent(parent(A))
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:AbstractGray,T<:Number,M} =
parent(A)
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:RGB,T<:Number,M} =
reshape(parent(A), Base.tail(axes(parent(A))))
if VERSION >= v"1.6.0-DEV.1083"
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C,AA,false}) where {C<:RGB,T<:Number,M,AA} =
reshape(parent(A), Base.tail(axes(parent(A))))
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C,AA,true}) where {C<:RGB,T<:Number,M,AA} =
parent(A)
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C,AA,false}) where {C<:Color,T<:Number,M,AA} =
reshape(parent(A), Base.tail(axes(parent(A))))
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C,AA,true}) where {C<:Color,T<:Number,M,AA} =
parent(A)
else
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:AbstractGray,T<:Number,M} =
parent(A)
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:RGB,T<:Number,M} =
reshape(parent(A), Base.tail(axes(parent(A))))
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:Color,T<:Number,M} =
reshape(parent(A), Base.tail(axes(parent(A))))
end
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:AbstractRGB,T<:Number,M} =
_colorview_reorder(C, A)
_ccolorview(::Type{C}, A::Base.ReinterpretArray{T,M,C}) where {C<:Color,T<:Number,M} =
reshape(parent(A), Base.tail(axes(parent(A))))
_ccolorview(::Type{C}, A::AbstractArray{T}) where {C<:Colorant,T<:Number} =
__ccolorview(C, A) # necessary to avoid ambiguities from dispatch on eltype
__ccolorview(::Type{C}, A::AbstractArray{T}) where {T<:Number,C<:RGB{T}} = reinterpretc(C, A)
Expand Down Expand Up @@ -178,7 +199,7 @@ Create a function that is equivalent to `(As...) -> colorview(C, Ax...)`.

```jldoctest; setup = :(using ImageCore)
julia> ones(Float32, 2, 2) |> colorview(Gray)
2×2 reinterpret(Gray{Float32}, ::$(Array{Float32,2})):
2×2 reinterpret($(VERSION >= v"1.6.0-DEV.1083" ? "reshape, " : "")Gray{Float32}, ::$(Array{Float32,2}))$(VERSION >= v"1.6.0-DEV.1083" ? " with eltype Gray{Float32}" : ""):
Gray{Float32}(1.0) Gray{Float32}(1.0)
Gray{Float32}(1.0) Gray{Float32}(1.0)
```
Expand Down
96 changes: 51 additions & 45 deletions src/convert_reinterpret.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,67 @@

@pure samesize(::Type{T}, ::Type{S}) where {T,S} = sizeof(T) == sizeof(S)

## Color->Color
# function reinterpretc(::Type{CV1}, a::Array{CV2,1}) where {CV1<:Colorant,CV2<:Colorant}
# CV = ccolor(CV1, CV2)
# l = (length(a)*sizeof(CV2))÷sizeof(CV1)
# l*sizeof(CV1) == length(a)*sizeof(CV2) || throw(ArgumentError("sizes are incommensurate"))
# reshape(reinterpret(CV, a), (l,))
# end
function reinterpretc(::Type{CV1}, a::AbstractArray{CV2}) where {CV1<:Colorant,CV2<:Colorant}
CV = ccolor(CV1, CV2)
if samesize(CV, CV2)
return reshape(reinterpret(CV, a), size(a))
if VERSION >= v"1.6.0-DEV.1083"
reinterpretc(::Type{T}, a::AbstractArray) where T = reinterpret(reshape, ccolor_number(T, eltype(a)), a)
reinterpretc(::Type{T}, a::Base.ReinterpretArray{S,N,T,AA,true}) where {T,S,N,AA<:AbstractArray{T}} = parent(a)
else
# Color->Color
function reinterpretc(::Type{CV1}, a::Array{CV2,1}) where {CV1<:Colorant,CV2<:Colorant}
CV = ccolor(CV1, CV2)
l = (length(a)*sizeof(CV2))÷sizeof(CV1)
l*sizeof(CV1) == length(a)*sizeof(CV2) || throw(ArgumentError("sizes are incommensurate"))
reshape(reinterpret(CV, a), (l,))
end
throw(ArgumentError("result shape not specified"))
end

## Color->T
function reinterpretc(::Type{T}, a::AbstractArray{CV}) where {T<:Number,CV<:Colorant}
if samesize(T, CV)
return reinterpret(T, a)
function reinterpretc(::Type{CV1}, a::AbstractArray{CV2}) where {CV1<:Colorant,CV2<:Colorant}
CV = ccolor(CV1, CV2)
if samesize(CV, CV2)
return reshape(reinterpret(CV, a), size(a))
end
throw(ArgumentError("result shape not specified"))
end
axs = axes(a)
if sizeof(CV) == sizeof(T)*_len(CV)
return reinterpret(T, reshape(a, Base.OneTo(1), axs...))

# Color->T
function reinterpretc(::Type{T}, a::AbstractArray{CV}) where {T<:Number,CV<:Colorant}
if samesize(T, CV)
return reinterpret(T, a)
end
axs = axes(a)
if sizeof(CV) == sizeof(T)*_len(CV)
return reinterpret(T, reshape(a, Base.OneTo(1), axs...))
end
throw(ArgumentError("result shape not specified"))
end
throw(ArgumentError("result shape not specified"))
end
reinterpretc(::Type{T}, a::AbstractArray{CV,0}) where {T<:Number,CV<:Colorant} =
reinterpret(T, reshape(a, 1))
reinterpretc(::Type{T}, a::AbstractArray{CV,0}) where {T<:Number,CV<:Colorant} =
reinterpret(T, reshape(a, 1))

_len(::Type{C}) where {C} = _len(C, eltype(C))
_len(::Type{C}, ::Type{Any}) where {C} = error("indeterminate type")
_len(::Type{C}, ::Type{T}) where {C,T} = sizeof(C) ÷ sizeof(T)
_len(::Type{C}) where {C} = _len(C, eltype(C))
_len(::Type{C}, ::Type{Any}) where {C} = error("indeterminate type")
_len(::Type{C}, ::Type{T}) where {C,T} = sizeof(C) ÷ sizeof(T)

## T->Color
# We have to distinguish two forms of call:
# form 1: reinterpretc(RGB{N0f8}, img)
# form 2: reinterpretc(RGB, img)
function reinterpretc(CV::Type{<:Colorant}, a::AbstractArray{T}) where T<:Number # {CV<:Colorant,T<:Number}
@noinline throwdm(C::Type, ind1) =
throw(DimensionMismatch("indices $ind1 are not consistent with color type $C"))
CVT = ccolor_number(CV, T)
if samesize(CVT, T)
return reinterpret(CVT, a)
end
axs = axes(a)
if axs[1] == Base.OneTo(sizeof(CVT) ÷ sizeof(eltype(CVT)))
return reshape(reinterpret(CVT, a), tail(axs))
## T->Color
# We have to distinguish two forms of call:
# form 1: reinterpretc(RGB{N0f8}, img)
# form 2: reinterpretc(RGB, img)
function reinterpretc(CV::Type{<:Colorant}, a::AbstractArray{T}) where T<:Number # {CV<:Colorant,T<:Number}
@noinline throwdm(C::Type, ind1) =
throw(DimensionMismatch("indices $ind1 are not consistent with color type $C"))
CVT = ccolor_number(CV, T)
if samesize(CVT, T)
return reinterpret(CVT, a)
end
axs = axes(a)
if axs[1] == Base.OneTo(sizeof(CVT) ÷ sizeof(eltype(CVT)))
return reshape(reinterpret(CVT, a), tail(axs))
end
throwdm(CV, axs[1])
end
throwdm(CV, axs[1])
end

# ccolor_number converts form 2 calls to form 1 calls
ccolor_number(::Type{CV}, ::Type{T}) where {CV<:Colorant,T<:Number} =
ccolor_number(::Type{T}, ::Any) where T<:Number = T
ccolor_number(::Type{CV}, ::Type{T}) where {CV<:Colorant,T} =
ccolor_number(CV, eltype(CV), T)
ccolor_number(::Type{CV}, ::Type{CVT}, ::Type{T}) where {CV,CVT<:Number,T} = CV # form 1
ccolor_number(::Type{CV}, ::Type{CVT}, ::Type{T}) where {CV,CVT<:Number,T} = CV # form 1
ccolor_number(::Type{CV}, ::Type{Any}, ::Type{T}) where {CV<:Colorant,T} = CV{T} # form 2

# for docstrings in the operations below
Expand Down
85 changes: 56 additions & 29 deletions test/benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ end
function test_getindex(f, ar, cv, n)
t_ar = Array{Float64}(undef, n)
t_cv = Array{Float64}(undef, n)
# Store the results to prevent the compiler from eliding the call
f_ar = Ref(f(ar))
f_cv = Ref(f(cv))
@test f_ar[] ≈ f_cv[] # but this also gives us a chance to test correctness
for i = 1:n
t_ar[i] = (tstart = time(); f_ar[] = f(ar); time()-tstart)
t_cv[i] = (tstart = time(); f_cv[] = f(cv); time()-tstart)
end
median(t_ar), median(t_cv), f_ar
median(t_ar), median(t_cv)
end
function test_setindex(f, ar, cv, n)
t_ar = Array{Float64}(undef, n)
Expand All @@ -72,45 +74,70 @@ function test_setindex(f, ar, cv, n)
median(t_ar), median(t_cv)
end

ssz = (1000,1000)
c = rand(RGB{Float64}, ssz...)
a = copy(reinterpretc(Float64, c))
vchan = channelview(c)
vcol = colorview(RGB, a)
cc_getindex_funcs = (mysum_elt_boundscheck,
mysum_index_boundscheck,
mysum_elt_inbounds,
mysum_index_inbounds_simd)
cc_setindex_funcs = (myfill1!,
myfill2!)
chanvtol = Dict(mysum_index_inbounds_simd => 20, # @simd doesn't work for ChannelView :(
mysum_elt_boundscheck => 20,
myfill1! => 20, # crappy setindex! performance
myfill2! => 20)
chanvdefault = 10
colvtol = Dict(mysum_elt_boundscheck=>5,
mysum_index_boundscheck=>5)

# Performance tolerances
isfast = VERSION >= v"1.6.0-DEV.1083"
chanvtol = Dict(mysum_index_inbounds_simd => isfast ? 3 : 20,
mysum_elt_boundscheck => isfast ? 3 : 20,
myfill1! => 20,
myfill2! => isfast ? 3 : 20)
chanvdefault = isfast ? 3 : 10
colvtol = Dict(mysum_elt_boundscheck=>isfast ? 3 : 5,
mysum_index_boundscheck=>isfast ? 3 : 5)
colvdefault = 3

ssz = (1000,300)

@info "Benchmark tests are warnings for now"
# @testset "benchmarks" begin
for (suite, testf) in ((cc_getindex_funcs, test_getindex),
(cc_setindex_funcs, test_setindex))
for f in suite
# Channelview
t_ar, t_cv = testf(f, a, vchan, 10^2)
tol = haskey(chanvtol, f) ? chanvtol[f] : chanvdefault
if t_cv >= tol*t_ar
@warn "ChannelView: failed on $f, time ratio $(t_cv/t_ar), tol $tol"
end
# @test t_cv < tol*t_ar
# ColorView
t_ar, t_cv = testf(f, c, vcol, 10^2)
tol = haskey(colvtol, f) ? colvtol[f] : colvdefault
if t_cv >= tol*t_ar
@warn "ColorView: failed on $f, time ratio $(t_cv/t_ar), tol $tol"
for T in (Float32, Float64)
c = rand(RGB{T}, ssz...)
a = copy(reinterpretc(T, c))
vchan = channelview(c)
vcol = colorview(RGB, a)

# view versions
rview = 2:ssz[1]-1
csub = view(c, rview, :)
asub = view(a, :, rview, :)
vchansub = channelview(csub)
vcolsub = colorview(RGB, asub)

for (suite, testf) in ((cc_getindex_funcs, test_getindex),
(cc_setindex_funcs, test_setindex))
for f in suite
# channelview
t_ar, t_cv = testf(f, a, vchan, 30)
tol = haskey(chanvtol, f) ? chanvtol[f] : chanvdefault
if t_cv >= tol*t_ar
@warn "channelview1: failed on $f with eltype $T, time ratio $(t_cv/t_ar), tol $tol"
end

t_ar, t_cv = testf(f, asub, vchansub, 30)
tol = haskey(chanvtol, f) ? chanvtol[f] : chanvdefault
if t_cv >= tol*t_ar
@warn "channelview2: failed on $f with eltype $T, time ratio $(t_cv/t_ar), tol $tol"
end

# colorview
t_ar, t_cv = testf(f, c, vcol, 30)
tol = haskey(colvtol, f) ? colvtol[f] : colvdefault
if t_cv >= tol*t_ar
@warn "colorview1: failed on $f with eltype $T, time ratio $(t_cv/t_ar), tol $tol"
end

t_ar, t_cv = testf(f, csub, vcolsub, 30)
tol = haskey(colvtol, f) ? colvtol[f] : colvdefault
if t_cv >= tol*t_ar
@warn "colorview2: failed on $f with eltype $T, time ratio $(t_cv/t_ar), tol $tol"
end
end
# @test t_cv < tol*t_ar
end
end
# end
2 changes: 1 addition & 1 deletion test/colorchannels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ end
@test @inferred(axes(v)) == (IdentityUnitRange(-1:1), IdentityUnitRange(-2:2))
@test @inferred(v[0,0]) === RGB(a[1,0,0], a[2,0,0], a[3,0,0])
a = OffsetArray(rand(3, 3, 5), 0:2, -1:1, -2:2)
@test_throws DimensionMismatch colorview(RGB, a)
@test_throws (VERSION >= v"1.6.0-DEV.1083" ? ArgumentError : DimensionMismatch) colorview(RGB, a)
end
end

Expand Down
4 changes: 2 additions & 2 deletions test/convert_reinterpret.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ using Test, Random

# indeterminate type tests
a = Array{RGB{AbstractFloat}}(undef, 3)
@test_throws ErrorException reinterpretc(Float64, a)
@test_throws Union{ArgumentError,ErrorException} reinterpretc(Float64, a)
a = Vector{RGB}(undef, 3)
@test_throws ErrorException reinterpretc(Float64, a)
@test_throws Union{ArgumentError,ErrorException} reinterpretc(Float64, a)

# Invalid conversions
a = rand(UInt8, 4,5)
Expand Down
18 changes: 12 additions & 6 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ else
sumsz(img) = ""
end

const rrstr = VERSION >= v"1.6.0-DEV.1083" ? "reshape, " : ""
rrdim(n) = VERSION >= v"1.6.0-DEV.1083" ? n-1 : n

# N0f8 is shown as either N0f8 or Normed{UInt8, 8}
# RGB is shown as ColorTypes.RGB or RGB
function typestring(::Type{T}) where T
Expand All @@ -22,13 +25,15 @@ RGB_str = typestring(RGB)
v = view(rgb32, 2:3, :)
@test summary(v) == "2×5 view(::Array{RGB{Float32},2}, 2:3, :) with eltype $(RGB_str){Float32}"
a = channelview(rgb32)
@test summary(a) == "3×3×5 reinterpret(Float32, ::Array{RGB{Float32},3})"
@test summary(a) == (VERSION >= v"1.6.0-DEV.1083" ? "3×3×5 reinterpret(reshape, Float32, ::Array{RGB{Float32},2}) with eltype Float32" :
"3×3×5 reinterpret(Float32, ::Array{RGB{Float32},3})")
num64 = rand(3,5)
b = colorview(RGB, num64)
@test summary(b) == "5-element reshape(reinterpret(RGB{Float64}, ::$(typeof(num64))), 5) with eltype $(RGB_str){Float64}"
@test summary(b) == (VERSION >= v"1.6.0-DEV.1083" ? "5-element reinterpret(reshape, RGB{Float64}, ::$(typeof(num64))) with eltype $(RGB_str){Float64}" :
"5-element reshape(reinterpret(RGB{Float64}, ::$(typeof(num64))), 5) with eltype $(RGB_str){Float64}")
rgb8 = rand(RGB{N0f8}, 3, 5)
c = rawview(channelview(rgb8))
@test summary(c) == "3×3×5 rawview(reinterpret(N0f8, ::Array{RGB{N0f8},3})) with eltype UInt8"
@test summary(c) == "3×3×5 rawview(reinterpret($(rrstr)N0f8, ::Array{RGB{N0f8},$(rrdim(3))})) with eltype UInt8"
@test summary(rgb8) == "3×5 Array{RGB{N0f8},2} with eltype $(RGB_str){$(N0f8_str)}"
rand8 = rand(UInt8, 3, 5)
d = normedview(PermutedDimsArray(rand8, (2,1)))
Expand All @@ -39,14 +44,15 @@ RGB_str = typestring(RGB)
f = PermutedDimsArray(normedview(N0f16, rand16), (2,1))
@test summary(f) == "5×3 PermutedDimsArray(reinterpret(N0f16, ::$(typeof(rand16))), (2, 1)) with eltype $(N0f16_str)"
g = channelview(rgb8)
@test summary(g) == "3×3×5 reinterpret(N0f8, ::Array{RGB{N0f8},3})"
etstr = VERSION >= v"1.6.0-DEV.1083" ? " with eltype N0f8" : ""
@test summary(g) == "3×3×5 reinterpret($(rrstr)N0f8, ::Array{RGB{N0f8},$(rrdim(3))})$etstr"
h = OffsetArray(rgb8, -1:1, -2:2)
@test summary(h) == "$(sumsz(h))OffsetArray(::Array{RGB{N0f8},2}, -1:1, -2:2) with eltype $(RGB_str){$(N0f8_str)} with indices -1:1×-2:2"
i = channelview(h)
@test summary(i) == "$(sumsz(i))reinterpret(N0f8, OffsetArray(::Array{RGB{N0f8},3}, 1:1, -1:1, -2:2)) with indices 1:3×-1:1×-2:2"
@test summary(i) == "$(sumsz(i))reinterpret($(rrstr)N0f8, OffsetArray(::Array{RGB{N0f8},$(rrdim(3))}, 1:1, -1:1, -2:2)) with indices 1:3×-1:1×-2:2"
c = channelview(rand(RGB{N0f8}, 2))
o = OffsetArray(c, -1:1, 0:1)
@test summary(o) == "$(sumsz(o))OffsetArray(reinterpret(N0f8, ::Array{RGB{N0f8},2}), -1:1, 0:1) with eltype $(N0f8_str) with indices -1:1×0:1"
@test summary(o) == "$(sumsz(o))OffsetArray(reinterpret($(rrstr)N0f8, ::Array{RGB{N0f8},$(rrdim(2))}), -1:1, 0:1) with eltype $(N0f8_str) with indices -1:1×0:1"
# Issue #45
a = collect(tuple())
@test summary(a) == "0-element $(typeof(a))"
Expand Down