Skip to content

Commit

Permalink
add im_to_matlab
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnychen94 committed Apr 11, 2022
1 parent 6262e64 commit 638a296
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/ImageCore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ export
width,
widthheight,
# matlab compatibility
im_from_matlab
im_from_matlab,
im_to_matlab


include("colorchannels.jl")
Expand Down
69 changes: 68 additions & 1 deletion src/matlab.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,71 @@ function _im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Colorant, T
# FIXME(johnnychen94): not type inferrable here
return StructArray{_CT}(X; dims=3)
end
_im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Gray, T<:Real} = of_eltype(CT, X)
_im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Gray, T<:Real} = colorview(CT, X)


"""
im_to_matlab([T], X::AbstractArray) -> AbstractArray{T}
Convert colorant array `X` to numerical array, using MATLAB's image layout convention.
```julia
img = rand(Gray{N0f8}, 4, 4)
im_to_matlab(img) # 4×4 array with element type N0f8
im_to_matlab(Float64, img) # 4×4 array with element type Float64
img = rand(RGB{N0f8}, 4, 4)
im_to_matlab(img) # 4×4×3 array with element type N0f8
im_to_matlab(Float64, img) # 4×4×3 array with element type Float64
```
For color image `X`, it will be converted to RGB colorspace first. The alpha channel, if
presented, will be removed.
```jldoctest; setup = :(using ImageCore, Random; Random.seed!(1234))
julia> img = Lab.(rand(RGB, 4, 4));
julia> im_to_matlab(img) ≈ im_to_matlab(RGB.(img))
true
julia> img = rand(AGray{N0f8}, 4, 4);
julia> im_to_matlab(img) ≈ im_to_matlab(gray.(img))
true
```
!!! tip "lazy conversion"
To save memory allocation, the conversion is done in lazy mode. In some cases, this
could introduce performance overhead due to the repeat computation. This can be easily
solved by converting eagerly via, e.g., `collect(im_to_matlab(...))`.
!!! info "value range"
The output value is always in range \$[0, 1]\$. Thus the equality
`data ≈ im_to_matlab(im_from_matlab(data))` only holds when `data` is in also range
\$[0, 1]\$. For example, if `eltype(data) == UInt8`, this equality will not hold.
See also: [`im_from_matlab`](@ref).
"""
function im_to_matlab end

im_to_matlab(X::AbstractArray{<:Number}) = X
im_to_matlab(img::AbstractArray{CT}) where CT<:Colorant = im_to_matlab(eltype(CT), img)

im_to_matlab(::Type{T}, img::AbstractArray{CT}) where {T,CT<:TransparentColor} =
im_to_matlab(T, of_eltype(base_color_type(CT), img))
im_to_matlab(::Type{T}, img::AbstractArray{<:Color}) where T =
im_to_matlab(T, of_eltype(RGB{T}, img))
im_to_matlab(::Type{T}, img::AbstractArray{<:Gray}) where T =
of_eltype(T, channelview(img))

# for RGB, only 1d and 2d cases are supported as other cases are not well-defined in MATLAB.
im_to_matlab(::Type{T}, img::AbstractVector{<:RGB}) where T =
im_to_matlab(T, reshape(img, (length(img), 1)))
im_to_matlab(::Type{T}, img::AbstractMatrix{<:RGB}) where T =
PermutedDimsArray(of_eltype(T, channelview(img)), (2, 3, 1))
im_to_matlab(::Type{T}, img::AbstractArray{<:RGB}) where T =
throw(ArgumentError("For $(ndims(img)) dimensional color image, manual conversion to MATLAB layout is required."))

# this method allows `data === im_to_matlab(im_from_matlab(data))` for gray image
im_to_matlab(::Type{T}, img::Base.ReinterpretArray{CT,N,T,<:AbstractArray{T,N}, true}) where {CT,N,T} =
img.parent
79 changes: 79 additions & 0 deletions test/matlab.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,83 @@
msg = "Unrecognized MATLAB image layout."
@test_throws ArgumentError(msg) im_from_matlab(data)
end

@testset "im_to_matlab" begin
@testset "Gray" begin
img = rand(Gray{N0f8}, 4, 5)
data = @inferred im_to_matlab(img)
@test eltype(data) == N0f8
@test size(data) == (4, 5)
@test img == data
data = @inferred im_to_matlab(Float64, img)
@test eltype(data) == Float64
@test img == data

img = rand(Gray{Float64}, 4, 5)
data = @inferred im_to_matlab(img)
@test eltype(data) == Float64
@test size(data) == (4, 5)
@test img == data

img = rand(UInt8, 4, 5)
@test img === @inferred im_to_matlab(img)

img = rand(Gray{Float64}, 4)
data = @inferred im_to_matlab(img)
@test eltype(data) == Float64
@test size(data) == (4, )
end

@testset "RGB" begin
img = rand(RGB{N0f8}, 4, 5)
data = @inferred im_to_matlab(img)
@test eltype(data) == N0f8
@test size(data) == (4, 5, 3)
@test permutedims(channelview(img), (2, 3, 1)) == data
data = @inferred im_to_matlab(Float64, img)
@test eltype(data) == Float64
@test size(data) == (4, 5, 3)
@test permutedims(channelview(img), (2, 3, 1)) == data

img = rand(RGB{Float64}, 4, 5)
data = @inferred im_to_matlab(img)
@test eltype(data) == Float64
@test size(data) == (4, 5, 3)
@test permutedims(channelview(img), (2, 3, 1)) == data

img = rand(UInt8, 4, 5, 3)
@test img === @inferred im_to_matlab(img)

img = rand(RGB{Float64}, 4)
data = @inferred im_to_matlab(img)
@test eltype(data) == Float64
@test size(data) == (4, 1, 3) # oh yes, we add one extra dimension for RGB but not for Gray

img = rand(RGB{Float64}, 2, 3, 4)
msg = "For 3 dimensional color image, manual conversion to MATLAB layout is required."
@test_throws ArgumentError(msg) im_to_matlab(img)
end

@testset "Color3" begin
img = Lab.(rand(RGB, 4, 5))
@test @inferred(im_to_matlab(img)) @inferred(im_to_matlab(RGB.(img)))
end
@testset "transparent" begin
img = rand(AGray, 4, 5)
@test @inferred(im_to_matlab(img)) == @inferred(im_to_matlab(Gray.(img)))
img = rand(RGBA, 4, 5)
@test @inferred(im_to_matlab(img)) == @inferred(im_to_matlab(RGB.(img)))
end
end

# test `im_from_matlab` and `im_to_matlab` are inverses of each other.
data = rand(4, 5)
@test data === im_to_matlab(im_from_matlab(data))
# For RGB, ideally we would want to ensure this === equality, but it's not possible at the moment.
data = rand(4, 5, 3)
@test data == im_to_matlab(im_from_matlab(data))
# the output range are always in [0, 1]; in this case they're not inverse of each other.
data = rand(UInt8, 4, 5)
img = im_from_matlab(data)
@test im_to_matlab(img) == data ./ 255
end

0 comments on commit 638a296

Please sign in to comment.