diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index b1f5fe7..00e415b 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: ['1.0', '1', 'nightly'] + julia-version: ['1.6', '1', 'nightly'] os: [ubuntu-latest] arch: [x64] include: @@ -41,7 +41,7 @@ jobs: env: cache-name: cache-artifacts with: - path: ~/.julia/artifacts + path: ~/.julia/artifacts key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} restore-keys: | ${{ runner.os }}-test-${{ env.cache-name }}- diff --git a/NEWS.md b/NEWS.md index 0859137..df95579 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# 0.10.0 + +## Breaking changes + +- `clamp01` has been restricted to `AbstractGray` and `AbstractRGB`, as it did not make sense for colors like `HSV` (#181) +- Step 3 of [ColorVectorSpace's `abs2` transition has been completed](https://github.com/JuliaGraphics/ColorVectorSpace.jl#abs-and-abs2); users and developers should now replace any `ColorVectorSpace.Future.abs2` calls with `abs2` to prevent breakage when the final step completes. + +## Other changes + +- AbstractFFTs.jl is no longer a dependency; error hints are now used for `fft` operations on deliberately-unsupported `Colorant` types +- Graphics.jl is no longer a dependency +- PrecompileTools.jl is now used for precompilation + # 0.7.0 ## Breaking changes diff --git a/Project.toml b/Project.toml index bee7a06..5be2f2a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,47 +1,45 @@ name = "ImageCore" uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534" -version = "0.9.3" +version = "0.10.1" [deps] -AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c" ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -Graphics = "a2bd30eb-e257-5431-a919-1863eab51364" IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959" MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" MosaicViews = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" PaddedViews = "5432bcbf-9aad-5242-b902-cca2824c8663" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" [compat] -AbstractFFTs = "0.4, 0.5, 1.0" -ColorVectorSpace = "0.9.7" +ColorVectorSpace = "0.10" Colors = "0.12" FixedPointNumbers = "0.8" -Graphics = "0.4, 1.0" IndirectArrays = "0.5, 1" MappedArrays = "0.2, 0.3, 0.4" MosaicViews = "0.3.3" OffsetArrays = "0.8, 0.9, 0.10, 0.11, 1.0.1" PaddedViews = "0.5.8" +PrecompileTools = "1" Reexport = "0.2, 1.0" StructArrays = "0.5, 0.6" -julia = "1" +julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4" +BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" -ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "ColorVectorSpace", "Documenter", "FFTW", "ImageInTerminal", "ImageMagick", "Random", "ReferenceTests", "Statistics", "Test"] +test = ["Aqua", "BlockArrays", "Documenter", "FFTW", "ImageIO", "ImageInTerminal", "Random", "ReferenceTests", "Statistics", "Test"] diff --git a/docs/src/views.md b/docs/src/views.md index 7bb2f63..2dc793a 100644 --- a/docs/src/views.md +++ b/docs/src/views.md @@ -139,7 +139,7 @@ julia> img RGB{N0f8}(0.0,0.0,1.0) RGB{N0f8}(0.0,0.0,0.0) ``` -The hexidecimal representation of 128 is 0x80; this is approximately +The hexadecimal representation of 128 is 0x80; this is approximately halfway to 255, and as a consequence the `N0f8` representation is very near 0.5. You can see the same change is reflected in `r`, `v`, and `img`: there is only one underlying array, `img`, and the two diff --git a/src/ImageCore.jl b/src/ImageCore.jl index 2c7a43c..54438bd 100644 --- a/src/ImageCore.jl +++ b/src/ImageCore.jl @@ -7,7 +7,7 @@ using Reexport @reexport using MosaicViews @reexport using PaddedViews -using MappedArrays, Graphics +using MappedArrays using OffsetArrays # for show.jl using OffsetArrays: no_offset_view using .ColorTypes: colorant_string @@ -29,30 +29,24 @@ end using Base: tail, @pure, Indices import Base: float -import Graphics: width, height - # TODO: just use .+ # See https://github.com/JuliaLang/julia/pull/22932#issuecomment-330711997 plus(r::AbstractUnitRange, i::Integer) = broadcast(+, r, i) plus(a::AbstractArray, i::Integer) = a .+ i using .ColorTypes: AbstractGray, TransparentGray, Color3, Transparent3 -Color1{T} = Colorant{T,1} -Color2{T} = Colorant{T,2} -Color4{T} = Colorant{T,4} -AColor{N,C,T} = AlphaColor{C,T,N} -ColorA{N,C,T} = ColorAlpha{C,T,N} +const Color1{T} = Colorant{T,1} +const Color2{T} = Colorant{T,2} +const Color4{T} = Colorant{T,4} +const AColor{N,C,T} = AlphaColor{C,T,N} +const ColorA{N,C,T} = ColorAlpha{C,T,N} const NonparametricColors = Union{RGB24,ARGB32,Gray24,AGray32} -Color1Array{C<:Color1,N} = AbstractArray{C,N} +const Color1Array{C<:Color1,N} = AbstractArray{C,N} # Type that arises from reshape(reinterpret(To, A), sz): -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 RRArray{To,From,M,P} = Base.ReinterpretArray{To,M,From,P,true} 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 +# Deliberately not export these constants to enable extensibility for downstream packages const NumberLike = Union{Number,AbstractGray} const Pixel = Union{Number,Colorant} const GenericGrayImage{T<:NumberLike,N} = AbstractArray{T,N} @@ -94,7 +88,6 @@ export # traits assert_timedim_last, coords_spatial, - height, indices_spatial, namedaxes, nimages, @@ -109,14 +102,12 @@ export im_from_matlab, im_to_matlab - include("colorchannels.jl") include("stackedviews.jl") include("convert_reinterpret.jl") include("traits.jl") include("map.jl") include("show.jl") -include("functions.jl") include("matlab.jl") include("deprecations.jl") @@ -188,9 +179,23 @@ function Base.transpose(a::AbstractVector{C}) where C<:Colorant out end -if VERSION >= v"1.4.2" # work around https://github.com/JuliaLang/julia/issues/34121 - include("precompile.jl") - _precompile_() +# It's better not to define fft on Colorant arrays, because keeping +# track of the color dimension and the fft-dims is prone to omissions +# or problems due to later operations. So we put the bookkeeping on +# the user, but we try to give helpful suggestions. +function throw_ffterror(io, @nospecialize(f), x, dims=1:ndims(x)) + newdims = plus(dims, channelview_dims_offset(x)) + print(io, '\n', f, " not defined for eltype $(eltype(x)). Use channelview, and likely $newdims for the dims in the fft.") end +function __init__() + Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs + if nameof(exc.f) ∈ (:fft, :rfft, :plan_fft, :plan_rfft, :realfloat) && argtypes[1] <: AbstractArray{<:Colorant} + throw_ffterror(io, exc.f, exc.args...) + end + end +end + +include("precompile.jl") + end ## module diff --git a/src/colorchannels.jl b/src/colorchannels.jl index 811c835..ffca438 100644 --- a/src/colorchannels.jl +++ b/src/colorchannels.jl @@ -27,22 +27,19 @@ dimorder(::Type{<:AlphaColor{<:Color3,T,N}}) where {T,N} = ColorChanPerm((4, 1, const ColorChanPermSubArray{T,N,P,I<:Tuple,L} = SubArray{T,N,P,I,L} -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 +const RRPermArray{To,From,M,P<:ColorChanPermSubArray} = + RRArray{To,From,M,P} # This type exists solely to set multiple values in the color channel axis struct NVector{T,N} <: AbstractVector{T} v::NTuple{N,T} + + NVector{T,N}(x::NTuple{N}) where {T,N} = new{T,N}(x) end Base.IndexStyle(::Type{<:NVector}) = IndexLinear() Base.size(v::NVector{T,N}) where {T,N} = (N,) Base.getindex(v::NVector, i::Int) = v.v[i] -NVector(x::Vararg{T,N}) where {T,N} = NVector{T,N}(x) +NVector(x1::T, x::Vararg{T,N}) where {T,N} = NVector{T,N+1}((x1, x...)) @inline Base.setindex!(A::RRPermArray{<:RGBX,<:Number,N}, val::AbstractRGB, i::Vararg{Int,N}) where N = setindex!(parent(parent(parent(A))), NVector(red(val), green(val), blue(val)), :, i...) @@ -66,13 +63,8 @@ A = channelview(img) # a 3×10×10 array See also: [`colorview`](@ref) """ channelview(A::AbstractArray{T}) where {T<:Number} = 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::RRArray{<:Colorant,<:Number}) = parent(A) +channelview(A::RRPermArray{<:Colorant,<:Number}) = parent(parent(A)) 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} @@ -117,25 +109,14 @@ _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)) -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,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) _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) @@ -198,7 +179,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($(VERSION >= v"1.6.0-DEV.1083" ? "reshape, " : "")Gray{Float32}, ::$(Array{Float32,2}))$(VERSION >= v"1.6.0-DEV.1083" ? " with eltype Gray{Float32}" : ""): +2×2 reinterpret(reshape, Gray{Float32}, ::$(Array{Float32,2})) with eltype Gray{Float32}: Gray{Float32}(1.0) Gray{Float32}(1.0) Gray{Float32}(1.0) Gray{Float32}(1.0) ``` diff --git a/src/convert_reinterpret.jl b/src/convert_reinterpret.jl index 6ce1cde..c87b8d1 100644 --- a/src/convert_reinterpret.jl +++ b/src/convert_reinterpret.jl @@ -2,61 +2,8 @@ @pure samesize(::Type{T}, ::Type{S}) where {T,S} = sizeof(T) == sizeof(S) -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 - 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 - - # 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 - 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) - - ## 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 -end +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) # ccolor_number converts form 2 calls to form 1 calls ccolor_number(::Type{T}, ::Any) where T<:Number = T diff --git a/src/deprecations.jl b/src/deprecations.jl index e59a676..a03a3b4 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -1,19 +1,15 @@ const warned_once = Ref(false) -if VERSION >= v"1.5" - function forced_depwarn(msg, sym) - opts = Base.JLOptions() - if !warned_once[] && !(opts.depwarn == 1) - @warn msg - @info """It is recommended that you fix this now to avoid breakage when a new version is released and this warning is removed. - Tip: to see all deprecation warnings together with code locations, launch Julia with `--depwarn=yes` and rerun your code.""" - warned_once[] = true - else - Base.depwarn(msg, sym) - end - return nothing +function forced_depwarn(msg, sym) + opts = Base.JLOptions() + if !warned_once[] && !(opts.depwarn == 1) + @warn msg + @info """It is recommended that you fix this now to avoid breakage when a new version is released and this warning is removed. + Tip: to see all deprecation warnings together with code locations, launch Julia with `--depwarn=yes` and rerun your code.""" + warned_once[] = true + else + Base.depwarn(msg, sym) end -else - forced_depwarn(msg, sym) = Base.depwarn(msg, sym) + return nothing end # a perhaps "permanent" deprecation diff --git a/src/functions.jl b/src/functions.jl deleted file mode 100644 index 8e6f553..0000000 --- a/src/functions.jl +++ /dev/null @@ -1,20 +0,0 @@ -import AbstractFFTs: fft, rfft, plan_fft, plan_rfft - -# It's better not to define fft on Colorant arrays, because keeping -# track of the color dimension and the fft-dims is prone to omissions -# or problems due to later operations. So we put the bookkeeping on -# the user, but we try to give helpful suggestions. - -function throw_ffterror(f, x, dims=1:ndims(x)) - newdims = plus(dims, channelview_dims_offset(x)) - error("$f not defined for eltype $(eltype(x)). Use channelview, and likely $newdims for the dims in the fft.") -end - -for f in (:fft, :rfft) - pf = Symbol("plan_", f) - @eval begin - $f(x::AbstractArray{C}) where {C<:Colorant} = throw_ffterror($f, x) - $f(x::AbstractArray{C}, dims) where {C<:Colorant} = throw_ffterror($f, x, dims) - $pf(x::AbstractArray{C}; kws...) where {C<:Colorant} = throw_ffterror($pf, x) - end -end diff --git a/src/map.jl b/src/map.jl index 8ccd44f..7c2e798 100644 --- a/src/map.jl +++ b/src/map.jl @@ -11,8 +11,8 @@ channel separately. See also: [`clamp01!`](@ref), [`clamp01nan`](@ref). """ clamp01(x::Union{N0f8,N0f16}) = x -clamp01(x::Number) = clamp(x, zero(x), oneunit(x)) -clamp01(c::Colorant) = mapc(clamp01, c) +clamp01(x::NumberLike) = clamp(x, zero(x), oneunit(x)) +clamp01(c::Union{TransparentGray,AbstractRGB,TransparentRGB}) = mapc(clamp01, c) """ clamp01!(array::AbstractArray) @@ -20,7 +20,7 @@ clamp01(c::Colorant) = mapc(clamp01, c) Restrict values in array to [0, 1], in-place. See also [`clamp01`](@ref). """ function clamp01!(img::AbstractArray) - # slgihtly faster than map!(clamp01, img, img) + # Slightly faster than map!(clamp01, img, img) @inbounds for i in eachindex(img) img[i] = clamp01(img[i]) end @@ -35,8 +35,8 @@ Similar to `clamp01`, except that any `NaN` values are changed to 0. See also: [`clamp01nan!`](@ref), [`clamp01`](@ref). """ clamp01nan(x) = clamp01(x) -clamp01nan(x::AbstractFloat) = ifelse(isnan(x), zero(x), clamp01(x)) -clamp01nan(c::Colorant) = mapc(clamp01nan, c) +clamp01nan(x::Union{AbstractFloat,AbstractGray{<:AbstractFloat}}) = ifelse(isnan(x), zero(x), clamp01(x)) +clamp01nan(c::Union{TransparentGray,AbstractRGB,TransparentRGB}) = mapc(clamp01nan, c) """ clamp01nan!(array::AbstractArray) diff --git a/src/precompile.jl b/src/precompile.jl index 382bac8..67fe15b 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -1,109 +1,103 @@ -macro warnpcfail(ex::Expr) - modl = __module__ - file = __source__.file === nothing ? "?" : String(__source__.file) - line = __source__.line - quote - $(esc(ex)) || @warn """precompile directive - $($(Expr(:quote, ex))) - failed. Please report an issue in $($modl) (after checking for duplicates) or remove this directive.""" _file=$file _line=$line - end -end +using PrecompileTools -function pcarray(f::F, ::Type{A}, sz) where {F,A} - a = f(A(undef, sz)) - fill!(a, zero(eltype(a))) - return first(a) -end -function pcm(a1, a2; fillvalue = zero(eltype(a1))) - v = mosaic(a1, a2; fillvalue=fillvalue) - return first(v) -end -function pcmv(a; fillvalue = zero(eltype(a))) - v = mosaicview(a; fillvalue=fillvalue) - return first(v) -end - -function _precompile_() - ccall(:jl_generating_output, Cint, ()) == 1 || return nothing - eltypes = (N0f8, N0f16, Float32, Float64) # eltypes of parametric colors - pctypes = (Gray, RGB, AGray, GrayA, ARGB, RGBA) # parametric colors - cctypes = (Gray24, AGray32, RGB24, ARGB32) # non-parametric colors - dims = (1, 2, 3, 4) - szs = ((2,), (2, 2), (2, 2, 2), (2, 2, 2, 2)) - - for T in eltypes - @warnpcfail precompile(clamp01, (T,)) - @warnpcfail precompile(clamp01nan, (T,)) - @warnpcfail precompile(scaleminmax, (T, T)) - @warnpcfail precompile(scalesigned, (T,)) - @warnpcfail precompile(scalesigned, (T,T,T)) - for C in pctypes - @warnpcfail precompile(clamp01, (C{T},)) - @warnpcfail precompile(clamp01nan, (C{T},)) - @warnpcfail precompile(colorsigned, (C{T},C{T})) - end +let +@setup_workload begin + function pcarray(f::F, ::Type{A}, sz) where {F,A} + a = f(A(undef, sz)) + fill!(a, zero(eltype(a))) + return first(a) end - for C in cctypes - @warnpcfail precompile(clamp01, (C,)) - @warnpcfail precompile(clamp01nan, (C,)) - @warnpcfail precompile(colorsigned, (C,C)) + function pcm(a1, a2; fillvalue = zero(eltype(a1))) + v = mosaic(a1, a2; fillvalue=fillvalue) + return first(v) end - # For the arrays, it's better to make them and exercise them so we get the getindex/setindex! - # methods precompiled too. - for sz in szs - for T in eltypes - T <: FixedPoint || continue - pcarray(rawview, Array{T,length(sz)}, sz) - end - pcarray(normedview, Array{UInt8,length(sz)}, sz) - pcarray(a->normedview(N0f8, a), Array{UInt8, length(sz)}, sz) - pcarray(a->normedview(N0f16, a), Array{UInt16,length(sz)}, sz) + function pcmv(a; fillvalue = zero(eltype(a))) + v = mosaicview(a; fillvalue=fillvalue) + return first(v) end - for sz in szs + + eltypes = (N0f8, N0f16, Float32, Float64) # eltypes of parametric colors + pctypes = (Gray, RGB) # parametric colors + cctypes = () # non-parametric colors (e.g., Gray24) + dims = (1, 2, 3, 4) + szs = ((2,), (2, 2), (2, 2, 2), (2, 2, 2, 2)) + @compile_workload begin for T in eltypes + clamp01(zero(T)) + clamp01nan(zero(T)) + scaleminmax(zero(T), oneunit(T)) + scalesigned(oneunit(T)) + scalesigned(zero(T), oneunit(T) / 2, oneunit(T)) for C in pctypes - nc = sizeof(C{T}) ÷ sizeof(T) - if nc == 1 - pcarray(a->colorview(C, a), Array{T,length(sz)}, sz) - pcarray(a->colorview(C{T}, a), Array{T,length(sz)}, sz) - else - pcarray(a->colorview(C, a), Array{T,length(sz)+1}, (nc, sz...)) - pcarray(a->colorview(C{T}, a), Array{T,length(sz)+1}, (nc, sz...)) - end - pcarray(channelview, Array{C{T},length(sz)}, sz) - if T<:FixedPoint - R = FixedPointNumbers.rawtype(T) + clamp01(zero(C{T})) + clamp01nan(zero(C{T})) + colorsigned(zero(C{T}), oneunit(C{T})) + end + end + for C in cctypes + clamp01(zero(C)) + clamp01nan(zero(C)) + C === AGray32 && continue + colorsigned(zero(C), oneunit(C)) + end + # For the arrays, it's better to make them and exercise them so we get the getindex/setindex! + # methods precompiled too. + for sz in szs + for T in eltypes + T <: FixedPoint || continue + pcarray(rawview, Array{T,length(sz)}, sz) + end + pcarray(normedview, Array{UInt8,length(sz)}, sz) + pcarray(a->normedview(N0f8, a), Array{UInt8, length(sz)}, sz) + pcarray(a->normedview(N0f16, a), Array{UInt16,length(sz)}, sz) + end + for sz in szs + for T in eltypes + for C in pctypes + nc = sizeof(C{T}) ÷ sizeof(T) if nc == 1 - pcarray(a->colorview(C, normedview(T, a)), Array{R,length(sz)}, sz) - pcarray(a->colorview(C{T}, normedview(T, a)), Array{R,length(sz)}, sz) + pcarray(a->colorview(C, a), Array{T,length(sz)}, sz) + pcarray(a->colorview(C{T}, a), Array{T,length(sz)}, sz) else - pcarray(a->colorview(C, normedview(T, a)), Array{R,length(sz)+1}, (nc, sz...)) - pcarray(a->colorview(C{T}, normedview(T, a)), Array{R,length(sz)+1}, (nc, sz...)) + pcarray(a->colorview(C, a), Array{T,length(sz)+1}, (nc, sz...)) + pcarray(a->colorview(C{T}, a), Array{T,length(sz)+1}, (nc, sz...)) + end + pcarray(channelview, Array{C{T},length(sz)}, sz) + if T<:FixedPoint + R = FixedPointNumbers.rawtype(T) + if nc == 1 + pcarray(a->colorview(C, normedview(T, a)), Array{R,length(sz)}, sz) + pcarray(a->colorview(C{T}, normedview(T, a)), Array{R,length(sz)}, sz) + else + pcarray(a->colorview(C, normedview(T, a)), Array{R,length(sz)+1}, (nc, sz...)) + pcarray(a->colorview(C{T}, normedview(T, a)), Array{R,length(sz)+1}, (nc, sz...)) + end end end end + T, C = Bool, Gray + pcarray(a->colorview(C, a), Array{T,length(sz)}, sz) + pcarray(a->colorview(C{T}, a), Array{T,length(sz)}, sz) + pcarray(channelview, Array{C{T},length(sz)}, sz) end - T, C = Bool, Gray - pcarray(a->colorview(C, a), Array{T,length(sz)}, sz) - pcarray(a->colorview(C{T}, a), Array{T,length(sz)}, sz) - pcarray(channelview, Array{C{T},length(sz)}, sz) - end - for T in eltypes - a = zeros(T, (2, 2)) - a3 = zeros(T, (2, 2, 2)) - pcm(a, a) - pcmv(a3) - for C in (Gray, RGB, GrayA, RGBA) - a = zeros(C{T}, (2, 2)) - a3 = zeros(C{T}, (2, 2, 3)) + for T in eltypes + a = zeros(T, (2, 2)) + a3 = zeros(T, (2, 2, 2)) pcm(a, a) pcmv(a3) - if C === RGB - pcm(a, a; fillvalue=zero(Gray{T})) - elseif C === RGBA - pcm(a, a; fillvalue=zero(GrayA{T})) + for C in (Gray, RGB, GrayA, RGBA) + a = zeros(C{T}, (2, 2)) + a3 = zeros(C{T}, (2, 2, 3)) + pcm(a, a) + pcmv(a3) + if C === RGB + pcm(a, a; fillvalue=zero(Gray{T})) + elseif C === RGBA + pcm(a, a; fillvalue=zero(GrayA{T})) + end end end + pcm(zeros(Float32, 2, 2), zeros(Float64, 2, 2)) # heterogeneous end - pcm(zeros(Float32, 2, 2), zeros(Float64, 2, 2)) # heterogeneous +end end diff --git a/src/show.jl b/src/show.jl index dfc9f25..cd53e3f 100644 --- a/src/show.jl +++ b/src/show.jl @@ -1,5 +1,5 @@ # rawview -AAFixed{T<:FixedPoint,N} = AbstractArray{T,N} +const AAFixed{T<:FixedPoint,N} = AbstractArray{T,N} function Base.showarg(io::IO, A::MappedArray{T,N,AA,typeof(reinterpret)}, toplevel=false) where {T<:Integer,N,AA<:AAFixed} print(io, "rawview(") Base.showarg(io, parent(A), false) @@ -8,7 +8,7 @@ function Base.showarg(io::IO, A::MappedArray{T,N,AA,typeof(reinterpret)}, toplev end # normedview -AAInteger{T<:Integer,N} = AbstractArray{T,N} +const AAInteger{T<:Integer,N} = AbstractArray{T,N} function Base.showarg(io::IO, A::MappedArray{T,N,AA,F,typeof(reinterpret)}, toplevel=false) where {T<:FixedPoint,N,AA<:AAInteger,F} print(io, "normedview(") ColorTypes.showcoloranttype(io, T) diff --git a/src/stackedviews.jl b/src/stackedviews.jl index b6abd6a..c35d3bf 100644 --- a/src/stackedviews.jl +++ b/src/stackedviews.jl @@ -102,7 +102,8 @@ struct ZeroArray{T,N,R<:AbstractUnitRange} <: AbstractArray{T,N} inds::NTuple{N,R} end -ZeroArrayPromise{T}(inds::NTuple{N,R}) where {T,N,R<:AbstractUnitRange} = ZeroArray{T,N,R}(inds) +ZeroArrayPromise{T}(inds::Tuple{R,Vararg{R,N}}) where {T,N,R<:AbstractUnitRange} = ZeroArray{T,N+1,R}(inds) +ZeroArrayPromise{T}(inds::NTuple{N,AbstractUnitRange}) where {T,N} = ZeroArrayPromise{T}(promote(inds...)) Base.eltype(::Type{ZeroArrayPromise{T}}) where {T} = T Base.axes(A::ZeroArray) = A.inds diff --git a/src/traits.jl b/src/traits.jl index c7c38c2..8ef6694 100644 --- a/src/traits.jl +++ b/src/traits.jl @@ -200,10 +200,6 @@ end assert_timedim_last(img::OffsetArray) = assert_timedim_last(parent(img)) assert_timedim_last(img::SubArray) = assert_timedim_last(parent(img)) -widthheight(img::AbstractArray) = length(axes(img,2)), length(axes(img,1)) - -width(img::AbstractArray) = widthheight(img)[1] -height(img::AbstractArray) = widthheight(img)[2] # Traits whose only meaningful definitions occur in ImageAxes, but for diff --git a/test/colorchannels.jl b/test/colorchannels.jl index aea99bc..77e29fa 100644 --- a/test/colorchannels.jl +++ b/test/colorchannels.jl @@ -1,5 +1,6 @@ using Colors, ImageCore, OffsetArrays, FixedPointNumbers, Test using OffsetArrays: IdentityUnitRange +using BlockArrays # backward-compatibility to ColorTypes < v0.9 or Colors < v0.11 using ImageCore: XRGB, RGBX @@ -407,7 +408,21 @@ 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 (VERSION >= v"1.6.0-DEV.1083" ? ArgumentError : DimensionMismatch) colorview(RGB, a) + @test_throws ArgumentError colorview(RGB, a) + end + + @testset "Custom/divergent axis types" begin + img1 = rand(5, 4, 2) + img2_2 = mortar(reshape([rand(5, 4, 1), rand(5, 4, 1)], 1, 1, 2)) + img2_all = mortar(reshape([rand(5, 4, 1), rand(5, 4, 1), rand(5, 4, 1)], 1, 1, 3)) + img2_odd = img2_all[:,:,1:2:end] + for img2 in (img2_2, img2_odd) + for imgrgb in (colorview(RGB, img1, img2, zeroarray), + colorview(RGB, img2, img1, zeroarray)) + @test eltype(imgrgb) === RGB{Float64} + @test size(imgrgb) == size(img1) + end + end end end diff --git a/test/functions.jl b/test/functions.jl index b6fb01c..837ccdf 100644 --- a/test/functions.jl +++ b/test/functions.jl @@ -7,15 +7,12 @@ using FFTW (rfft, (ag,)), (rfft, (ag, 1:2)), (plan_rfft, (ag,)), (fft, (ac,)), (fft, (ac, 1:2)), (plan_fft, (ac,)), (rfft, (ac,)), (rfft, (ac, 1:2)), (plan_rfft, (ac,))) - ret = @test_throws ErrorException f(args...) - @test occursin("channelview", ret.value.msg) - @test occursin(eltype(args[1])<:Gray ? "1:2" : "2:3", ret.value.msg) + dims_str = eltype(args[1])<:Gray ? "1:2" : "2:3" + ret = @test_throws "channelview, and likely $dims_str" f(args...) end for (a, dims) in ((ag, 1:2), (ac, 2:3)) @test ifft(fft(channelview(a), dims), dims) ≈ channelview(a) - ret = @test_throws ErrorException rfft(a) - @test occursin("channelview", ret.value.msg) - @test occursin("$dims", ret.value.msg) + ret = @test_throws "channelview, and likely $dims" rfft(a) @test irfft(rfft(channelview(a), dims), 4, dims) ≈ channelview(a) end diff --git a/test/runtests.jl b/test/runtests.jl index 6627608..01bb3f8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,18 +6,20 @@ using Test, ReferenceTests using Aqua, Documenter # for meta quality checks @static if VERSION >= v"1.3" - @testset "Project meta quality checks" begin - # Not checking compat section for test-only dependencies - Aqua.test_ambiguities(ImageCore) - Aqua.test_all(ImageCore; + @testset "Project meta quality checks" begin + # Not checking compat section for test-only dependencies + Aqua.test_ambiguities(ImageCore) + Aqua.test_all(ImageCore; ambiguities=false, project_extras=true, deps_compat=true, stale_deps=true, + # FIXME? re-enable the `piracy` test + piracy=false, # currently just `float` and `paddedviews` project_toml_formatting=true, - unbound_args=false, # FIXME: it fails when this is true - ) - DocMeta.setdocmeta!(ImageCore, :DocTestSetup, :(using ImageCore); recursive=true) + unbound_args=true, + ) + DocMeta.setdocmeta!(ImageCore, :DocTestSetup, :(using ImageCore); recursive=true) end end @@ -34,8 +36,9 @@ include("views.jl") include("convert_reinterpret.jl") include("traits.jl") include("map.jl") -include("functions.jl") include("matlab.jl") +Base.VERSION >= v"1.8" && include("functions.jl") # requires @test_throws msg expr + include("show.jl") # To ensure our deprecations work and don't break code diff --git a/test/show.jl b/test/show.jl index c521b16..ad8db2d 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1,13 +1,9 @@ using ImageCore, Colors, FixedPointNumbers, OffsetArrays, Test -if VERSION >= v"1.2.0-DEV.229" - sumsz(img) = Base.dims2string(size(img)) * ' ' -else - sumsz(img) = "" -end +sumsz(img) = Base.dims2string(size(img)) * ' ' -const rrstr = VERSION >= v"1.6.0-DEV.1083" ? "reshape, " : "" -rrdim(n) = VERSION >= v"1.6.0-DEV.1083" ? n-1 : n +const rrstr = "reshape, " +rrdim(n) = n-1 # N0f8 is shown as either N0f8 or Normed{UInt8, 8} # RGB is shown as ColorTypes.RGB or RGB @@ -25,8 +21,7 @@ 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) == (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})") + @test summary(a) == "3×3×5 reinterpret(reshape, Float32, ::Array{RGB{Float32},2}) with eltype Float32" num64 = rand(3,5) b = colorview(RGB, num64) str = summary(b) @@ -51,11 +46,10 @@ RGB_str = typestring(RGB) @test occursin("5×3 PermutedDimsArray(reinterpret", str) && occursin("N0f16", str) && occursin("::$(typeof(rand16))), (2, 1)", str) && occursin("with eltype $(N0f16_str)", str) g = channelview(rgb8) - etstr = VERSION >= v"1.6.0-DEV.1083" ? " with eltype" : "" str = summary(g) @test occursin("3×3×5 reinterpret($(rrstr)", str) && occursin("N0f8", str) && occursin("::Array{RGB{N0f8},$(rrdim(3))}", str) - VERSION >= v"1.6.0-DEV.1083" && @test occursin("with eltype", str) + @test occursin("with eltype", str) 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) diff --git a/test/traits.jl b/test/traits.jl index 866af39..6ee24f0 100644 --- a/test/traits.jl +++ b/test/traits.jl @@ -29,8 +29,6 @@ using ImageCore: Pixel, NumberLike, GenericImage, GenericGrayImage, default_name @test indices_spatial(B) == (Base.OneTo(3), Base.OneTo(5)) end assert_timedim_last(B) - @test width(B) == 5 - @test height(B) == 3 end end diff --git a/test/views.jl b/test/views.jl index 2668de0..897927b 100644 --- a/test/views.jl +++ b/test/views.jl @@ -70,7 +70,7 @@ end @test V[1,:,:] == A @test all(iszero, V[2,:,:]) @test V[3,:,:] == B - err = VERSION >= v"1.9.0-DEV.351" ? CanonicalIndexError : ErrorException + err = VERSION >= v"1.8.0-rc1" ? CanonicalIndexError : ErrorException @test_throws err V[2,1,1] = 7 V32 = @inferred(StackedView{Float32}(A, zeroarray, B)) @test eltype(V32) == Float32 @@ -107,7 +107,7 @@ end @test @inferred(v[1,2]) === GrayA{N0f8}(0.25, 0.25) v = @inferred(colorview(GrayA{N0f8}, a, zeroarray)) @test @inferred(v[2,1]) === GrayA{N0f8}(0.3,0) - err = VERSION >= v"1.9.0-DEV.351" ? CanonicalIndexError : ErrorException + err = VERSION >= v"1.8.0-rc1" ? CanonicalIndexError : ErrorException @test_throws err (v[1,2] = GrayA(0.25, 0.25)) # RGB v = @inferred(colorview(RGB{N0f8}, a, zeroarray, b))