Skip to content

Commit 638a296

Browse files
committed
add im_to_matlab
1 parent 6262e64 commit 638a296

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

src/ImageCore.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export
9494
width,
9595
widthheight,
9696
# matlab compatibility
97-
im_from_matlab
97+
im_from_matlab,
98+
im_to_matlab
9899

99100

100101
include("colorchannels.jl")

src/matlab.jl

+68-1
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,71 @@ function _im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Colorant, T
9494
# FIXME(johnnychen94): not type inferrable here
9595
return StructArray{_CT}(X; dims=3)
9696
end
97-
_im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Gray, T<:Real} = of_eltype(CT, X)
97+
_im_from_matlab(::Type{CT}, X::AbstractArray{T}) where {CT<:Gray, T<:Real} = colorview(CT, X)
98+
99+
100+
"""
101+
im_to_matlab([T], X::AbstractArray) -> AbstractArray{T}
102+
103+
Convert colorant array `X` to numerical array, using MATLAB's image layout convention.
104+
105+
```julia
106+
img = rand(Gray{N0f8}, 4, 4)
107+
im_to_matlab(img) # 4×4 array with element type N0f8
108+
im_to_matlab(Float64, img) # 4×4 array with element type Float64
109+
110+
img = rand(RGB{N0f8}, 4, 4)
111+
im_to_matlab(img) # 4×4×3 array with element type N0f8
112+
im_to_matlab(Float64, img) # 4×4×3 array with element type Float64
113+
```
114+
115+
For color image `X`, it will be converted to RGB colorspace first. The alpha channel, if
116+
presented, will be removed.
117+
118+
```jldoctest; setup = :(using ImageCore, Random; Random.seed!(1234))
119+
julia> img = Lab.(rand(RGB, 4, 4));
120+
121+
julia> im_to_matlab(img) ≈ im_to_matlab(RGB.(img))
122+
true
123+
124+
julia> img = rand(AGray{N0f8}, 4, 4);
125+
126+
julia> im_to_matlab(img) ≈ im_to_matlab(gray.(img))
127+
true
128+
```
129+
130+
!!! tip "lazy conversion"
131+
To save memory allocation, the conversion is done in lazy mode. In some cases, this
132+
could introduce performance overhead due to the repeat computation. This can be easily
133+
solved by converting eagerly via, e.g., `collect(im_to_matlab(...))`.
134+
135+
!!! info "value range"
136+
The output value is always in range \$[0, 1]\$. Thus the equality
137+
`data ≈ im_to_matlab(im_from_matlab(data))` only holds when `data` is in also range
138+
\$[0, 1]\$. For example, if `eltype(data) == UInt8`, this equality will not hold.
139+
140+
See also: [`im_from_matlab`](@ref).
141+
"""
142+
function im_to_matlab end
143+
144+
im_to_matlab(X::AbstractArray{<:Number}) = X
145+
im_to_matlab(img::AbstractArray{CT}) where CT<:Colorant = im_to_matlab(eltype(CT), img)
146+
147+
im_to_matlab(::Type{T}, img::AbstractArray{CT}) where {T,CT<:TransparentColor} =
148+
im_to_matlab(T, of_eltype(base_color_type(CT), img))
149+
im_to_matlab(::Type{T}, img::AbstractArray{<:Color}) where T =
150+
im_to_matlab(T, of_eltype(RGB{T}, img))
151+
im_to_matlab(::Type{T}, img::AbstractArray{<:Gray}) where T =
152+
of_eltype(T, channelview(img))
153+
154+
# for RGB, only 1d and 2d cases are supported as other cases are not well-defined in MATLAB.
155+
im_to_matlab(::Type{T}, img::AbstractVector{<:RGB}) where T =
156+
im_to_matlab(T, reshape(img, (length(img), 1)))
157+
im_to_matlab(::Type{T}, img::AbstractMatrix{<:RGB}) where T =
158+
PermutedDimsArray(of_eltype(T, channelview(img)), (2, 3, 1))
159+
im_to_matlab(::Type{T}, img::AbstractArray{<:RGB}) where T =
160+
throw(ArgumentError("For $(ndims(img)) dimensional color image, manual conversion to MATLAB layout is required."))
161+
162+
# this method allows `data === im_to_matlab(im_from_matlab(data))` for gray image
163+
im_to_matlab(::Type{T}, img::Base.ReinterpretArray{CT,N,T,<:AbstractArray{T,N}, true}) where {CT,N,T} =
164+
img.parent

test/matlab.jl

+79
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,83 @@
124124
msg = "Unrecognized MATLAB image layout."
125125
@test_throws ArgumentError(msg) im_from_matlab(data)
126126
end
127+
128+
@testset "im_to_matlab" begin
129+
@testset "Gray" begin
130+
img = rand(Gray{N0f8}, 4, 5)
131+
data = @inferred im_to_matlab(img)
132+
@test eltype(data) == N0f8
133+
@test size(data) == (4, 5)
134+
@test img == data
135+
data = @inferred im_to_matlab(Float64, img)
136+
@test eltype(data) == Float64
137+
@test img == data
138+
139+
img = rand(Gray{Float64}, 4, 5)
140+
data = @inferred im_to_matlab(img)
141+
@test eltype(data) == Float64
142+
@test size(data) == (4, 5)
143+
@test img == data
144+
145+
img = rand(UInt8, 4, 5)
146+
@test img === @inferred im_to_matlab(img)
147+
148+
img = rand(Gray{Float64}, 4)
149+
data = @inferred im_to_matlab(img)
150+
@test eltype(data) == Float64
151+
@test size(data) == (4, )
152+
end
153+
154+
@testset "RGB" begin
155+
img = rand(RGB{N0f8}, 4, 5)
156+
data = @inferred im_to_matlab(img)
157+
@test eltype(data) == N0f8
158+
@test size(data) == (4, 5, 3)
159+
@test permutedims(channelview(img), (2, 3, 1)) == data
160+
data = @inferred im_to_matlab(Float64, img)
161+
@test eltype(data) == Float64
162+
@test size(data) == (4, 5, 3)
163+
@test permutedims(channelview(img), (2, 3, 1)) == data
164+
165+
img = rand(RGB{Float64}, 4, 5)
166+
data = @inferred im_to_matlab(img)
167+
@test eltype(data) == Float64
168+
@test size(data) == (4, 5, 3)
169+
@test permutedims(channelview(img), (2, 3, 1)) == data
170+
171+
img = rand(UInt8, 4, 5, 3)
172+
@test img === @inferred im_to_matlab(img)
173+
174+
img = rand(RGB{Float64}, 4)
175+
data = @inferred im_to_matlab(img)
176+
@test eltype(data) == Float64
177+
@test size(data) == (4, 1, 3) # oh yes, we add one extra dimension for RGB but not for Gray
178+
179+
img = rand(RGB{Float64}, 2, 3, 4)
180+
msg = "For 3 dimensional color image, manual conversion to MATLAB layout is required."
181+
@test_throws ArgumentError(msg) im_to_matlab(img)
182+
end
183+
184+
@testset "Color3" begin
185+
img = Lab.(rand(RGB, 4, 5))
186+
@test @inferred(im_to_matlab(img)) @inferred(im_to_matlab(RGB.(img)))
187+
end
188+
@testset "transparent" begin
189+
img = rand(AGray, 4, 5)
190+
@test @inferred(im_to_matlab(img)) == @inferred(im_to_matlab(Gray.(img)))
191+
img = rand(RGBA, 4, 5)
192+
@test @inferred(im_to_matlab(img)) == @inferred(im_to_matlab(RGB.(img)))
193+
end
194+
end
195+
196+
# test `im_from_matlab` and `im_to_matlab` are inverses of each other.
197+
data = rand(4, 5)
198+
@test data === im_to_matlab(im_from_matlab(data))
199+
# For RGB, ideally we would want to ensure this === equality, but it's not possible at the moment.
200+
data = rand(4, 5, 3)
201+
@test data == im_to_matlab(im_from_matlab(data))
202+
# the output range are always in [0, 1]; in this case they're not inverse of each other.
203+
data = rand(UInt8, 4, 5)
204+
img = im_from_matlab(data)
205+
@test im_to_matlab(img) == data ./ 255
127206
end

0 commit comments

Comments
 (0)