diff --git a/Project.toml b/Project.toml index 24380ce..2a57ca4 100644 --- a/Project.toml +++ b/Project.toml @@ -26,6 +26,7 @@ julia = ">= 1.0" ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [targets] -test = ["ColorVectorSpace", "Statistics", "Test"] +test = ["ColorVectorSpace", "Statistics", "Test", "Random"] diff --git a/src/ImageCore.jl b/src/ImageCore.jl index 830c33a..2536c38 100644 --- a/src/ImageCore.jl +++ b/src/ImageCore.jl @@ -40,6 +40,8 @@ const GenericImage{T<:Pixel,N} = AbstractArray{T,N} export ## Types + HasDimNames, + HasProperties, StackedView, ## constants zeroarray, @@ -75,6 +77,7 @@ export coords_spatial, height, indices_spatial, + namedaxes, nimages, pixelspacing, sdims, diff --git a/src/traits.jl b/src/traits.jl index b412acf..fa06f52 100644 --- a/src/traits.jl +++ b/src/traits.jl @@ -1,3 +1,56 @@ +""" + HasProperties(img) -> HasProperties{::Bool} + +Returns the trait `HasProperties`, indicating whether `x` has `properties` +method. +""" +struct HasProperties{T} end + +HasProperties(img::T) where T = HasProperties(T) + +HasProperties(::Type{T}) where T = HasProperties{false}() + +""" + HasDimNames(img) -> HasDimNames{::Bool} + +Returns the trait `HasDimNames`, indicating whether `x` has named dimensions. +Types returning `HasDimNames{true}()` should also have a `names` method that +returns a tuple of symbols for each dimension. +""" +struct HasDimNames{T} end + +HasDimNames(img::T) where T = HasDimNames(T) + +HasDimNames(::Type{T}) where T = HasDimNames{false}() + +""" + namedaxes(img) -> NamedTuple{names}(axes) + +Returns a `NamedTuple` where the names are the dimension names and each indice +is the corresponding dimensions's axis. If `HasDimNames` is not defined for `x` +default names are returned. `x` should have an `axes` method. + +```jldoctest +julia> using ImagesCore + +julia> img = reshape(1:24, 2,3,4); + +julia> namedaxes(img) +(dim_1 = Base.OneTo(2), dim_2 = Base.OneTo(3), dim_3 = Base.OneTo(4)) +``` +""" +namedaxes(img::T) where T = namedaxes(HasDimNames(T), img) + +namedaxes(::HasDimNames{true}, x::T) where T = NamedTuple{names(x)}(axes(x)) + +function namedaxes(::HasDimNames{false}, img::AbstractArray{T,N}) where {T,N} + NamedTuple{default_names(Val(N))}(axes(img)) +end + +@generated function default_names(img::Val{N}) where {N} + :($(ntuple(i -> Symbol(:dim_, i), N))) +end + """ pixelspacing(img) -> (sx, sy, ...) diff --git a/test/traits.jl b/test/traits.jl index 0c1a6ac..6c57442 100644 --- a/test/traits.jl +++ b/test/traits.jl @@ -1,6 +1,6 @@ using ImageCore, Colors, FixedPointNumbers, ColorVectorSpace, MappedArrays, OffsetArrays using Test -using ImageCore: Pixel, NumberLike, GenericImage, GenericGrayImage +using ImageCore: Pixel, NumberLike, GenericImage, GenericGrayImage, default_names @testset "Image traits" begin for (B, swap) in ((rand(UInt16(1):UInt16(20), 3, 5), false), @@ -158,4 +158,32 @@ end end end +struct RowVector{T,P} <: AbstractVector{T} + v::Vector{T} + p::P +end + +ImageCore.HasDimNames(::Type{<:RowVector}) = HasDimNames{true}() + +ImageCore.HasProperties(::Type{<:RowVector}) = HasProperties{true}() + +Base.names(::RowVector) = (:row,) +Base.axes(rv::RowVector) = axes(rv.v) + + +@testset "Trait Interface" begin + img = reshape(1:24, 2,3,4) + @test @inferred(namedaxes(img)) == NamedTuple{(:dim_1, :dim_2, :dim_3)}(axes(img)) + @test @inferred(HasDimNames(img)) == HasDimNames{false}() + @test @inferred(HasProperties(img)) == HasProperties{false}() + + rv = RowVector([1:10...], Dict{String,Any}()) + @test @inferred(HasDimNames(rv)) == HasDimNames{true}() + @test @inferred(HasProperties(rv)) == HasProperties{true}() + @test @inferred(namedaxes(rv)) == NamedTuple{(:row,)}((Base.OneTo(10),)) + + # default names + @test @inferred(default_names(Val(3))) == (:dim_1, :dim_2, :dim_3) +end + nothing