diff --git a/src/ImageCore.jl b/src/ImageCore.jl index ccbc3a7..5866863 100644 --- a/src/ImageCore.jl +++ b/src/ImageCore.jl @@ -76,6 +76,9 @@ export # traits assert_timedim_last, coords_spatial, + findaxis, + finddim, + hasdim, height, indices_spatial, namedaxes, diff --git a/src/traits.jl b/src/traits.jl index fa06f52..3582af9 100644 --- a/src/traits.jl +++ b/src/traits.jl @@ -31,26 +31,83 @@ 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> using ImageCore 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)) +(:row = Base.OneTo(2), :col = Base.OneTo(3), :page = Base.OneTo(4)) ``` """ -namedaxes(img::T) where T = namedaxes(HasDimNames(T), img) +namedaxes(img::T) where T = _namedaxes(HasDimNames(T), img) +_namedaxes(::HasDimNames{true}, img) = NamedTuple{names(img)}(axes(img)) +_namedaxes(::HasDimNames{false}, img) = NamedTuple{default_names(img)}(axes(img)) + +default_names(img::AbstractArray{T,N}) where {T,N} = ntuple(i->default_name(i), N) + +@inline function default_name(i::Integer) + if i == 1 + return :row + elseif i == 2 + return :col + elseif i == 3 + return :page + else + return Symbol(:dim_, i) + end +end -namedaxes(::HasDimNames{true}, x::T) where T = NamedTuple{names(x)}(axes(x)) +""" + findaxis(img, name) -> axis -function namedaxes(::HasDimNames{false}, img::AbstractArray{T,N}) where {T,N} - NamedTuple{default_names(Val(N))}(axes(img)) +Returns the axis (as in `axes(img, dim)`) that corresponds to the `name`. If no +matching name is found any empy axis (i.e. `0:0`) is returned. +""" +@inline findaxis(A, name::Symbol) = _findaxis(namedaxes(A), name) +function _findaxis(nt::NamedTuple{syms}, name::Symbol) where {syms} + if __hasdim(syms, name) + return getfield(nt, name) + else + return 0:-1 + end end -@generated function default_names(img::Val{N}) where {N} - :($(ntuple(i -> Symbol(:dim_, i), N))) +""" + finddim(img, name) -> Int + +Returns the dimension that has the corresponding `name`. If `name` doesn't +match any of the dimension names `0` is returned. If `img` doesn't have `names` +then the default set of names is searched (e.g., dim_1, dim_2, ...). +""" +@inline finddim(A::T, name::Symbol) where {T} = _finddim(HasDimNames(T), A, name) +_finddim(::HasDimNames{false}, A::T, name::Symbol) where {T} = __finddim(default_names(A), name) +_finddim(::HasDimNames{true}, A::T, name::Symbol) where {T} = __finddim(names(A), name) + +Base.@pure function __finddim(dimnames::NTuple{N,Symbol}, name::Symbol) where {N} + for i in 1:N + getfield(dimnames, i) === name && return i + end + return 0 end + +""" + hasdim(x, name) -> Bool + +Returns `true` if `name` is present. +""" +hasdim(x::T, name::Symbol) where {T} = _hasdim(HasDimNames(T), x, name) +_hasdim(::HasDimNames{true}, x::T, name::Symbol) where {T} = __hasdim(names(x), name) +_hasdim(::HasDimNames{false}, x::T, name::Symbol) where {T} = __hasdim(default_names(x), name) + +Base.@pure function __hasdim(dimnames::Tuple{Vararg{Symbol,N}}, name::Symbol) where {N} + for ii in 1:N + getfield(dimnames, ii) === name && return true + end + return false +end + + """ pixelspacing(img) -> (sx, sy, ...) diff --git a/test/traits.jl b/test/traits.jl index 6c57442..2eaf3aa 100644 --- a/test/traits.jl +++ b/test/traits.jl @@ -169,21 +169,38 @@ ImageCore.HasProperties(::Type{<:RowVector}) = HasProperties{true}() Base.names(::RowVector) = (:row,) Base.axes(rv::RowVector) = axes(rv.v) - +Base.size(rv::RowVector) = size(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(namedaxes(img)) == NamedTuple{(:row, :col, :page)}(axes(img)) @test @inferred(HasDimNames(img)) == HasDimNames{false}() @test @inferred(HasProperties(img)) == HasProperties{false}() - rv = RowVector([1:10...], Dict{String,Any}()) + 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),)) + @test @inferred(hasdim(rv, :row)) == true + @test @inferred(hasdim(rv, :time)) == false + @test @inferred(hasdim(img, :time)) == false + + @test @inferred(finddim(rv, :time)) == 0 + @test @inferred(finddim(rv, :row)) == 1 + @test @inferred(finddim(img, :page)) == 3 + + @test findaxis(rv, :time) == 0:-1 + @test findaxis(rv, :row) == Base.OneTo(10) + @test findaxis(img, :page) == Base.OneTo(4) + # default names - @test @inferred(default_names(Val(3))) == (:dim_1, :dim_2, :dim_3) + @test @inferred(default_names(img)) == (:row, :col, :page) + @test @inferred(ImageCore.default_name(1)) == :row + @test @inferred(ImageCore.default_name(2)) == :col + @test @inferred(ImageCore.default_name(3)) == :page + @test @inferred(ImageCore.default_name(4)) == :dim_4 + end nothing