From 0171772176e30558d6c502743d5583a24888378d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 14 Sep 2016 03:28:39 -0500 Subject: [PATCH 01/38] Initial rebase on JuliaImages packages --- src/Images.jl | 66 +-- src/algorithms.jl | 1288 +++++++-------------------------------------- src/edge.jl | 145 ----- src/map.jl | 81 +-- 4 files changed, 231 insertions(+), 1349 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index c6ce1d17..02ec3268 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -1,4 +1,4 @@ -__precompile__(true) +__precompile__(false) # because of ImageAxes/ImageMeta module Images @@ -22,32 +22,36 @@ else export float32, float64 end +using Compat import Compat.view # "deprecated imports" are below -using Colors, ColorVectorSpace, FixedPointNumbers, FileIO, StatsBase +using Reexport +@reexport using FixedPointNumbers +@reexport using Colors +using ColorVectorSpace, FileIO +export load, save import Colors: Fractional, red, green, blue typealias AbstractGray{T} Color{T,1} typealias TransparentRGB{C<:AbstractRGB,T} TransparentColor{C,T,4} typealias TransparentGray{C<:AbstractGray,T} TransparentColor{C,T,2} -using Graphics +import Graphics import Graphics: width, height, Point -import FixedPointNumbers: ufixed8, ufixed10, ufixed12, ufixed14, ufixed16 -using Compat -import Compat.String - -using Base.Cartesian -include("compatibility/forcartesian.jl") # if isdefined(module_parent(Images), :Grid) # import ..Grid.restrict # end const is_little_endian = ENDIAN_BOM == 0x04030201 -immutable TypeConst{N} end # for passing compile-time constants to functions -include("core.jl") +@reexport using ImageCore +@reexport using ImageAxes +@reexport using ImageMetadata +@reexport using ImageFiltering + +using Base.Cartesian # TODO: delete this + include("map.jl") include("overlays.jl") include("labeledarrays.jl") @@ -59,34 +63,6 @@ include("writemime.jl") include("corner.jl") include("distances.jl") - -function precompile() - for T in (UInt8, UInt16, Int, Float32, Float64) - Tdiv = typeof(one(T)/2) - for N = 2:3 - precompile(restrict!, (Array{Tdiv, N}, Array{T,N}, Int)) - precompile(imfilter, (Array{T,N}, Array{Float64,N})) - precompile(imfilter, (Array{T,N}, Array{Float64,2})) - precompile(imfilter, (Array{T,N}, Array{Float32,N})) - precompile(imfilter, (Array{T,N}, Array{Float32,2})) - end - end - for T in (Float32, Float64) - for N = 2:3 - precompile(_imfilter_gaussian!, (Array{T,N}, Vector{Float64})) - precompile(_imfilter_gaussian!, (Array{T,N}, Vector{Int})) - precompile(imfilter_gaussian_no_nans!, (Array{T,N}, Vector{Float64})) - precompile(imfilter_gaussian_no_nans!, (Array{T,N}, Vector{Int})) - precompile(fft, (Array{T,N},)) - end - end - for T in (Complex{Float32}, Complex{Float64}) - for N = 2:3 - precompile(ifft, (Array{T,N},)) - end - end -end - export # types AbstractImage, AbstractImageDirect, @@ -281,7 +257,7 @@ export # types boxdiff, bilinear_interpolation, gaussian_pyramid, - + # distances hausdorff_distance, @@ -326,14 +302,4 @@ Test images and phantoms (see also TestImages.jl): """ Images -import FileIO: load, save -@deprecate imread(filename; kwargs...) load(filename; kwargs...) -@deprecate imwrite(img, filename; kwargs...) save(filename, img; kwargs...) -export load, save - -function limits(img) - Base.depwarn("limits is deprecated, all limits are (0,1)", :limits) - oldlimits(img) -end - end diff --git a/src/algorithms.jl b/src/algorithms.jl index ca423a72..ad78890b 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -1,80 +1,6 @@ -#### Math with images #### - - -(+)(img::AbstractImageDirect{Bool}, n::Bool) = img .+ n -(+)(n::Bool, img::AbstractImageDirect{Bool}) = n .+ img -(+)(img::AbstractImageDirect, n::Number) = img .+ n -(+)(img::AbstractImageDirect, n::AbstractRGB) = img .+ n -(+)(n::Number, img::AbstractImageDirect) = n .+ img -(+)(n::AbstractRGB, img::AbstractImageDirect) = n .+ img -(.+)(img::AbstractImageDirect, n::Number) = shareproperties(img, data(img).+n) -(.+)(n::Number, img::AbstractImageDirect) = shareproperties(img, data(img).+n) -if isdefined(:UniformScaling) - (+){Timg,TA<:Number}(img::AbstractImageDirect{Timg,2}, A::UniformScaling{TA}) = shareproperties(img, data(img)+A) - (-){Timg,TA<:Number}(img::AbstractImageDirect{Timg,2}, A::UniformScaling{TA}) = shareproperties(img, data(img)-A) -end -(+)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img)+A) -(+)(img::AbstractImageDirect, A::AbstractImageDirect) = shareproperties(img, data(img)+data(A)) -(+)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img)+data(A)) -(+){S,T}(A::Range{S}, img::AbstractImageDirect{T}) = shareproperties(img, data(A)+data(img)) -(+)(A::AbstractArray, img::AbstractImageDirect) = shareproperties(img, data(A)+data(img)) -(.+)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img).+A) -(.+)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img).+data(A)) -(-)(img::AbstractImageDirect{Bool}, n::Bool) = img .- n -(-)(img::AbstractImageDirect, n::Number) = img .- n -(-)(img::AbstractImageDirect, n::AbstractRGB) = img .- n -(.-)(img::AbstractImageDirect, n::Number) = shareproperties(img, data(img).-n) -(-)(n::Bool, img::AbstractImageDirect{Bool}) = n .- img -(-)(n::Number, img::AbstractImageDirect) = n .- img -(-)(n::AbstractRGB, img::AbstractImageDirect) = n .- img -(.-)(n::Number, img::AbstractImageDirect) = shareproperties(img, n.-data(img)) -(-)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img)-A) -(-){T}(img::AbstractImageDirect{T,2}, A::Diagonal) = shareproperties(img, data(img)-A) # fixes an ambiguity warning -(-)(img::AbstractImageDirect, A::Range) = shareproperties(img, data(img)-A) -(-)(img::AbstractImageDirect, A::AbstractImageDirect) = shareproperties(img, data(img)-data(A)) -(-)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img)-data(A)) -(-){S,T}(A::Range{S}, img::AbstractImageDirect{T}) = shareproperties(img, data(A)-data(img)) -(-)(A::AbstractArray, img::AbstractImageDirect) = shareproperties(img, data(A)-data(img)) -(-)(img::AbstractImageDirect) = shareproperties(img, -data(img)) -(.-)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img).-A) -(.-)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img).-data(A)) -(*)(img::AbstractImageDirect, n::Number) = (.*)(img, n) -(*)(n::Number, img::AbstractImageDirect) = (.*)(n, img) -(.*)(img::AbstractImageDirect, n::Number) = shareproperties(img, data(img).*n) -(.*)(n::Number, img::AbstractImageDirect) = shareproperties(img, data(img).*n) -(/)(img::AbstractImageDirect, n::Number) = shareproperties(img, data(img)/n) -(.*)(img1::AbstractImageDirect, img2::AbstractImageDirect) = shareproperties(img1, data(img1).*data(img2)) -(.*)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img).*A) -(.*)(A::BitArray, img::AbstractImageDirect) = shareproperties(img, data(img).*A) -(.*)(img::AbstractImageDirect{Bool}, A::BitArray) = shareproperties(img, data(img).*A) -(.*)(A::BitArray, img::AbstractImageDirect{Bool}) = shareproperties(img, data(img).*A) -(.*)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img).*A) -(.*)(A::AbstractArray, img::AbstractImageDirect) = shareproperties(img, data(img).*A) -(./)(img::AbstractImageDirect, A::BitArray) = shareproperties(img, data(img)./A) # needed to avoid ambiguity warning -(./)(img1::AbstractImageDirect, img2::AbstractImageDirect) = shareproperties(img1, data(img1)./data(img2)) -(./)(img::AbstractImageDirect, A::AbstractArray) = shareproperties(img, data(img)./A) -(.^)(img::AbstractImageDirect, p::Number) = shareproperties(img, data(img).^p) -sqrt(img::AbstractImageDirect) = @compat shareproperties(img, sqrt.(data(img))) -atan2(img1::AbstractImageDirect, img2::AbstractImageDirect) = - @compat shareproperties(img1, atan2.(data(img1),data(img2))) -hypot(img1::AbstractImageDirect, img2::AbstractImageDirect) = - @compat shareproperties(img1, hypot.(data(img1),data(img2))) -real(img::AbstractImageDirect) = @compat shareproperties(img,real.(data(img))) -imag(img::AbstractImageDirect) = @compat shareproperties(img,imag.(data(img))) -abs(img::AbstractImageDirect) = @compat shareproperties(img,abs.(data(img))) - Compat.@dep_vectorize_2arg Gray atan2 Compat.@dep_vectorize_2arg Gray hypot -function sum(img::AbstractImageDirect, region::Union{AbstractVector,Tuple,Integer}) - f = prod(size(img)[[region...]]) - out = copyproperties(img, sum(data(img), region)) - if in(colordim(img), region) - out["colorspace"] = "Unknown" - end - out -end - """ `M = meanfinite(img, region)` calculates the mean value along the dimensions listed in `region`, ignoring any non-finite values. """ @@ -89,53 +15,60 @@ function _meanfinite{T<:AbstractFloat}(A::AbstractArray, ::Type{T}, region) end _meanfinite(A::AbstractArray, ::Type, region) = mean(A, region) # non floating-point -function meanfinite{T<:AbstractFloat}(img::AbstractImageDirect{T}, region) - r = meanfinite(data(img), region) - out = copyproperties(img, r) - if in(colordim(img), region) - out["colorspace"] = "Unknown" - end - out -end -meanfinite(img::AbstractImageIndexed, region) = meanfinite(convert(Image, img), region) -# Note that you have to zero S and K upon entry -@generated function sumfinite!{T,N}(S, K, A::AbstractArray{T,N}) - quote - isempty(A) && return S, K - @nexprs $N d->(sizeS_d = size(S,d)) - sizeA1 = size(A, 1) - if size(S, 1) == 1 && sizeA1 > 1 - # When we are reducing along dim == 1, we can accumulate to a temporary - @inbounds @nloops $N i d->(d>1? (1:size(A,d)) : (1:1)) d->(j_d = sizeS_d==1 ? 1 : i_d) begin - s = @nref($N, S, j) - k = @nref($N, K, j) - for i_1 = 1:sizeA1 - tmp = @nref($N, A, i) - if isfinite(tmp) - s += tmp - k += 1 - end +using Base: check_reducedims, reducedim1, safe_tail +using Base.Broadcast: newindex, newindexer + +""" + sumfinite!(S, K, A) + +Compute the sum `S` and number of contributing pixels `K` for +reductions of the array `A` over dimensions. `S` and `K` must have +identical indices, and either match `A` or have singleton-dimensions for +the dimensions that are being summed over. Only pixels with finite +value are included in the tallies of `S` and `K`. + +Note that the pixel mean is just S./K. +""" +function sumfinite!{T,N}(S, K, A::AbstractArray{T,N}) + check_reducedims(S, A) + isempty(A) && return S, K + indices(S) == indices(K) || throw(DimensionMismatch("S and K must have identical indices")) + + indsAt, indsSt = safe_tail(indices(A)), safe_tail(indices(S)) + keep, Idefault = newindexer(indsAt, indsSt) + if reducedim1(S, A) + # keep the accumulators as a local variable when reducing along the first dimension + i1 = first(indices1(S)) + @inbounds for IA in CartesianRange(indsAt) + IS = newindex(IA, keep, Idefault) + s, k = S[i1,IS], K[i1,IS] + for i in indices(A, 1) + tmp = A[i, IA] + if isfinite(tmp) + s += tmp + k += 1 end - @nref($N, S, j) = s - @nref($N, K, j) = k end - else - # Accumulate to array storage - @inbounds @nloops $N i A d->(j_d = sizeS_d==1 ? 1 : i_d) begin - tmp = @nref($N, A, i) + S[i1,IS], K[i1,IS] = s, k + end + else + @inbounds for IA in CartesianRange(indsAt) + IS = newindex(IA, keep, Idefault) + for i in indices(A, 1) + tmp = A[i, IA] if isfinite(tmp) - @nref($N, S, j) += tmp - @nref($N, K, j) += 1 + S[i, IS] += tmp + K[i, IS] += 1 end end end - S, K end + S, K end # Entropy for grayscale (intensity) images function _log(kind::Symbol) - @compat if kind == :shannon + if kind == :shannon x -> log2.(x) elseif kind == :nat x -> log.(x) @@ -147,11 +80,14 @@ function _log(kind::Symbol) end """ -`entropy(img, kind)` is the entropy of a grayscale image defined as `-sum(p.*logb(p))`. + entropy(img, kind) + +Compute the entropy of a grayscale image defined as `-sum(p.*logb(p))`. The base b of the logarithm (a.k.a. entropy unit) is one of the following: - `:shannon ` (log base 2, default) - `:nat` (log base e) - `:hartley` (log base 10) + +- `:shannon ` (log base 2, default) +- `:nat` (log base e) +- `:hartley` (log base 10) """ function entropy(img::AbstractArray; kind=:shannon) logᵦ = _log(kind) @@ -177,16 +113,6 @@ end entropy{C<:AbstractGray}(img::AbstractArray{C}; kind=:shannon) = entropy(raw(img), kind=kind) -# Logical operations -(.<)(img::AbstractImageDirect, n::Number) = data(img) .< n -(.>)(img::AbstractImageDirect, n::Number) = data(img) .> n -(.<)(img::AbstractImageDirect{Bool}, A::AbstractArray{Bool}) = data(img) .< A -(.<)(img::AbstractImageDirect, A::AbstractArray) = data(img) .< A -(.>)(img::AbstractImageDirect, A::AbstractArray) = data(img) .> A -(.==)(img::AbstractImageDirect, n::Number) = data(img) .== n -(.==)(img::AbstractImageDirect{Bool}, A::AbstractArray{Bool}) = data(img) .== A -(.==)(img::AbstractImageDirect, A::AbstractArray) = data(img) .== A - # functions red, green, and blue for (funcname, fieldname) in ((:red, :r), (:green, :g), (:blue, :b)) fieldchar = string(fieldname)[1] @@ -296,37 +222,7 @@ sentinel_max{C<:AbstractGray}(::Type{C}) = _sentinel_max(C, eltype(C)) _sentinel_max{C<:AbstractGray,T}(::Type{C},::Type{T}) = C(sentinel_max(T)) -# fft & ifft -fft(img::AbstractImageDirect) = shareproperties(img, fft(data(img))) -function fft(img::AbstractImageDirect, region, args...) - F = fft(data(img), region, args...) - props = copy(properties(img)) - props["region"] = region - Image(F, props) -end -fft{CV<:Colorant}(img::AbstractImageDirect{CV}) = fft(img, 1:ndims(img)) -function fft{CV<:Colorant}(img::AbstractImageDirect{CV}, region, args...) - imgr = reinterpret(eltype(CV), img) - if ndims(imgr) > ndims(img) - newregion = ntuple(i->region[i]+1, length(region)) - else - newregion = ntuple(i->region[i], length(region)) - end - F = fft(data(imgr), newregion, args...) - props = copy(properties(imgr)) - props["region"] = newregion - Image(F, props) -end - -function ifft(img::AbstractImageDirect) - region = get(img, "region", 1:ndims(img)) - A = ifft(data(img), region) - props = copy(properties(img)) - haskey(props, "region") && delete!(props, "region") - Image(A, props) -end -ifft(img::AbstractImageDirect, region, args...) = ifft(data(img), region, args...) - +# FIXME: replace with IntegralImage # average filter """ `kern = imaverage(filtersize)` constructs a boxcar-filter of the specified size. @@ -342,20 +238,7 @@ function imaverage(filter_size=[3,3]) f = ones(Float64, m, n)/(m*n) end -# laplacian filter kernel -""" -`kern = imlaplacian(filtersize)` returns a kernel for laplacian filtering. -""" -function imlaplacian(diagonals::AbstractString="nodiagonals") - if diagonals == "diagonals" - return [1.0 1.0 1.0; 1.0 -8.0 1.0; 1.0 1.0 1.0] - elseif diagonals == "nodiagonals" - return [0.0 1.0 0.0; 1.0 -4.0 1.0; 0.0 1.0 0.0] - else - error("Expected \"diagnoals\" or \"nodiagonals\" or Number, got: \"$diagonals\"") - end -end - +# FIXME: do something about this # more general version function imlaplacian(alpha::Number) lc = alpha/(1 + alpha) @@ -364,68 +247,22 @@ function imlaplacian(alpha::Number) return [lc lb lc; lb lm lb; lc lb lc] end -# 2D gaussian filter kernel -""" -`kern = gaussian2d(sigma, filtersize)` returns a kernel for FIR-based Gaussian filtering. - -See also: `imfilter_gaussian`. -""" -function gaussian2d(sigma::Number=0.5, filter_size=[]) - if length(filter_size) == 0 - # choose 'good' size - m = 4*ceil(Int, sigma)+1 - n = m - elseif length(filter_size) != 2 - error("wrong filter size") - else - m, n = filter_size[1], filter_size[2] - end - if mod(m, 2) != 1 || mod(n, 2) != 1 - error("filter dimensions must be odd") +function sumdiff(f, A::AbstractArray, B::AbstractArray) + indices(A) == indices(B) || throw(DimensionMismatch("A and B must have the same indices")) + T = promote_type(difftype(eltype(A)), difftype(eltype(B))) + s = zero(accum(eltype(T))) + for (a, b) in zip(A, B) + x = convert(T, a) - convert(T, b) + s += f(x) end - g = Float64[exp(-(X.^2+Y.^2)/(2*sigma.^2)) for X=-floor(Int,m/2):floor(Int,m/2), Y=-floor(Int,n/2):floor(Int,n/2)] - return g/sum(g) + s end -# difference of gaussian -""" -`kern = imdog(sigma)` creates a difference-of-gaussians kernel (`sigma`s differing by a factor of -`sqrt(2)`). -""" -function imdog(sigma::Number=0.5) - m = 4*ceil(sqrt(2)*sigma)+1 - return gaussian2d(sqrt(2)*sigma, [m m]) - gaussian2d(sigma, [m m]) -end +"`s = ssd(A, B)` computes the sum-of-squared differences over arrays/images A and B" +ssd(A::AbstractArray, B::AbstractArray) = sumdiff(abs2, A, B) -# laplacian of gaussian -""" -`kern = imlog(sigma)` returns a laplacian-of-gaussian kernel. -""" -function imlog(sigma::Number=0.5) - m = ceil(8.5sigma) - m = m % 2 == 0 ? m + 1 : m - return [(1/(2pi*sigma^4))*(2 - (x^2 + y^2)/sigma^2)*exp(-(x^2 + y^2)/(2sigma^2)) - for x=-floor(m/2):floor(m/2), y=-floor(m/2):floor(m/2)] -end - -# Sum of squared differences and sum of absolute differences -for (f, op) in ((:ssd, :(abs2(x))), (:sad, :(abs(x)))) - @eval begin - function ($f)(A::AbstractArray, B::AbstractArray) - size(A) == size(B) || throw(DimensionMismatch("A and B must have the same size")) - T = promote_type(difftype(eltype(A)), difftype(eltype(B))) - s = zero(accum(eltype(T))) - for i = 1:length(A) - x = convert(T, A[i]) - convert(T, B[i]) - s += $op - end - s - end - end -end - -"`s = ssd(A, B)` computes the sum-of-squared differences over arrays/images A and B" ssd -"`s = sad(A, B)` computes the sum-of-absolute differences over arrays/images A and B" sad +"`s = sad(A, B)` computes the sum-of-absolute differences over arrays/images A and B" +sad(A::AbstractArray, B::AbstractArray) = sumdiff(abs, A, B) difftype{T<:Integer}(::Type{T}) = Int difftype{T<:Real}(::Type{T}) = Float32 @@ -520,539 +357,8 @@ function test_approx_eq_sigma_eps{T<:Real}(A::AbstractArray, B::AbstractArray, diffpct end -# Array padding -function padindexes{T,n}(img::AbstractArray{T,n}, prepad::Union{Vector{Int},Dims}, postpad::Union{Vector{Int},Dims}, border::AbstractString) - I = Array(Vector{Int}, n) - for d = 1:n - I[d] = padindexes(img, d, prepad[d], postpad[d], border) - end - I -end - """ -``` -imgpad = padarray(img, prepad, postpad, border, value) -``` - -For an `N`-dimensional array `img`, apply padding on both edges. `prepad` and -`postpad` are vectors of length `N` specifying the number of pixels used to pad -each dimension. `border` is a string, one of `"value"` (to pad with a specific -pixel value), `"replicate"` (to repeat the edge value), `"circular"` (periodic -boundary conditions), `"reflect"` (reflecting boundary conditions, where the -reflection is centered on edge), and `"symmetric"` (reflecting boundary -conditions, where the reflection is centered a half-pixel spacing beyond the -edge, so the edge value gets repeated). Arrays are automatically padded before -filtering. Use `"inner"` to avoid padding altogether; the output array will be -smaller than the input. -""" -function padarray{T,n}(img::AbstractArray{T,n}, prepad::Union{Vector{Int},Dims}, postpad::Union{Vector{Int},Dims}, border::AbstractString) - img[padindexes(img, prepad, postpad, border)...]::Array{T,n} -end -function padarray{n}(img::Union{BitArray{n}, SubArray{Bool,n,BitArray{n}}}, prepad::Union{Vector{Int},Dims}, postpad::Union{Vector{Int},Dims}, border::AbstractString) - img[padindexes(img, prepad, postpad, border)...]::BitArray{n} -end -function padarray{n,A<:BitArray}(img::Image{Bool,n,A}, prepad::Union{Vector{Int},Dims}, postpad::Union{Vector{Int},Dims}, border::AbstractString) - img[padindexes(img, prepad, postpad, border)...]::BitArray{n} -end - -function padarray{T,n}(img::AbstractArray{T,n}, prepad::Union{Vector{Int},Dims}, postpad::Union{Vector{Int},Dims}, border::AbstractString, value) - if border != "value" - return padarray(img, prepad, postpad, border) - end - A = Array(T, ntuple(d->size(img,d)+prepad[d]+postpad[d], n)) - fill!(A, value) - I = Vector{Int}[1+prepad[d]:size(A,d)-postpad[d] for d = 1:n] - A[I...] = img - A::Array{T,n} -end - -padarray{T,n}(img::AbstractArray{T,n}, padding::Union{Vector{Int},Dims}, border::AbstractString = "replicate") = padarray(img, padding, padding, border) -# Restrict the following to Number to avoid trouble when img is an Array{AbstractString} -padarray{T<:Number,n}(img::AbstractArray{T,n}, padding::Union{Vector{Int},Dims}, value::T) = padarray(img, padding, padding, "value", value) - -function padarray{T,n}(img::AbstractArray{T,n}, padding::Union{Vector{Int},Dims}, border::AbstractString, direction::AbstractString) - if direction == "both" - return padarray(img, padding, padding, border) - elseif direction == "pre" - return padarray(img, padding, zeros(Int, n), border) - elseif direction == "post" - return padarray(img, zeros(Int, n), padding, border) - end -end - -function padarray{T<:Number,n}(img::AbstractArray{T,n}, padding::Vector{Int}, value::T, direction::AbstractString) - if direction == "both" - return padarray(img, padding, padding, "value", value) - elseif direction == "pre" - return padarray(img, padding, zeros(Int, n), "value", value) - elseif direction == "post" - return padarray(img, zeros(Int, n), padding, "value", value) - end -end - - -function prep_kernel(img::AbstractArray, kern::AbstractArray) - sc = coords_spatial(img) - if ndims(kern) > length(sc) - error("""The kernel has $(ndims(kern)) dimensions, more than the $(sdims(img)) spatial dimensions of img. - You'll need to set the dimensions and type of the kernel to be the same as the image.""") - end - sz = ones(Int, ndims(img)) - for i = 1:ndims(kern) - sz[sc[i]] = size(kern,i) - end - reshape(kern, sz...) -end - - -### -### imfilter -### -""" -``` -imgf = imfilter(img, kernel, [border, value]) -``` - -filters the array `img` with the given `kernel`, using boundary conditions -specified by `border` and `value`. See `padarray` for an explanation of -the boundary conditions. Default is to use `"replicate"` boundary conditions. -This uses finite-impulse-response (FIR) filtering, and is fast only for -relatively small `kernel`s. - -See also: `imfilter_fft`, `imfilter_gaussian`. -""" -imfilter(img, kern, border, value) = imfilter_inseparable(img, kern, border, value) -# Do not combine these with the previous using a default value (see the 2d specialization below) -imfilter(img, filter) = imfilter(img, filter, "replicate", zero(eltype(img))) -imfilter(img, filter, border) = imfilter(img, filter, border, zero(eltype(img))) - -imfilter_inseparable{T,K,N,M}(img::AbstractArray{T,N}, kern::AbstractArray{K,M}, border::AbstractString, value) = - imfilter_inseparable(img, prep_kernel(img, kern), border, value) - -function imfilter_inseparable{T,K,N}(img::AbstractArray{T,N}, kern::AbstractArray{K,N}, border::AbstractString, value) - if border == "inner" - result = Array(typeof(one(T)*one(K)), ntuple(d->max(0, size(img,d)-size(kern,d)+1), N)) - imfilter!(result, img, kern) - else - prepad = [div(size(kern,i)-1, 2) for i = 1:N] - postpad = [div(size(kern,i), 2) for i = 1:N] - A = padarray(img, prepad, postpad, border, convert(T, value)) - result = imfilter!(Array(typeof(one(T)*one(K)), size(img)), A, data(kern)) - end - copyproperties(img, result) -end - -# Special case for 2d kernels: check for separability -function imfilter{T}(img::AbstractArray{T}, kern::AbstractMatrix, border::AbstractString, value) - sc = coords_spatial(img) - if length(sc) < 2 - imfilter_inseparable(img, kern, border, value) - end - SVD = svdfact(kern) - U, S, Vt = SVD[:U], SVD[:S], SVD[:Vt] - separable = true - EPS = sqrt(eps(eltype(S))) - for i = 2:length(S) - separable &= (abs(S[i]) < EPS) - end - if !separable - return imfilter_inseparable(img, kern, border, value) - end - s = S[1] - u,v = U[:,1],Vt[1,:] - ss = sqrt(s) - sz1 = ones(Int, ndims(img)); sz1[sc[1]] = size(kern, 1) - sz2 = ones(Int, ndims(img)); sz2[sc[2]] = size(kern, 2) - tmp = imfilter_inseparable(data(img), reshape(u*ss, sz1...), border, value) - copyproperties(img, imfilter_inseparable(tmp, reshape(v*ss, sz2...), border, value)) -end - -for N = 1:5 - @eval begin - function imfilter!{T,K}(B, A::AbstractArray{T,$N}, kern::AbstractArray{K,$N}) - for i = 1:$N - if size(B,i)+size(kern,i) > size(A,i)+1 - throw(DimensionMismatch("Output dimensions $(size(B)) and kernel dimensions $(size(kern)) do not agree with size of padded input, $(size(A))")) - end - end - (isempty(A) || isempty(kern)) && return B - p = A[1]*kern[1] - TT = typeof(p+p) - @nloops $N i B begin - tmp = zero(TT) - @inbounds begin - @nloops $N j kern d->(k_d = i_d+j_d-1) begin - tmp += (@nref $N A k)*(@nref $N kern j) - end - (@nref $N B i) = tmp - end - end - B - end - end -end - -""" -``` -imfilter!(dest, img, kernel) -``` - -filters the image with the given `kernel`, storing the output in the -pre-allocated output `dest`. The size of `dest` must not be greater than the -size of the result of `imfilter` with `border = "inner"`, and it behaves -identically. This uses finite-impulse-response (FIR) filtering, and is fast -only for relatively small `kernel`s. - -No padding is performed; see `padarray` for options if you want to do -this manually. - -See also: `imfilter`, `padarray`. -""" -imfilter! - -### -### imfilter_fft -### -""" -``` -imgf = imfilter_fft(img, kernel, [border, value]) -``` - -filters `img` with the given `kernel` using an FFT algorithm. This -is slower than `imfilter` for small kernels, but much faster for large -kernels. For Gaussian blur, an even faster choice is `imfilter_gaussian`. - -See also: `imfilter`, `imfilter_gaussian`. -""" -imfilter_fft(img, kern, border, value) = copyproperties(img, imfilter_fft_inseparable(img, kern, border, value)) -imfilter_fft(img, filter) = imfilter_fft(img, filter, "replicate", 0) -imfilter_fft(img, filter, border) = imfilter_fft(img, filter, border, 0) - -imfilter_fft_inseparable{T,K,N,M}(img::AbstractArray{T,N}, kern::AbstractArray{K,M}, border::AbstractString, value) = - imfilter_fft_inseparable(img, prep_kernel(img, kern), border, value) - -function imfilter_fft_inseparable{T<:Colorant,K,N,M}(img::AbstractArray{T,N}, kern::AbstractArray{K,M}, border::AbstractString, value) - A = reinterpret(eltype(T), data(img)) - kernrs = reshape(kern, tuple(1, size(kern)...)) - B = imfilter_fft_inseparable(A, prep_kernel(A, kernrs), border, value) - reinterpret(base_colorant_type(T), B) -end - -function imfilter_fft_inseparable{T<:Real,K,N}(img::AbstractArray{T,N}, kern::AbstractArray{K,N}, border::AbstractString, value) - if border == "circular" && size(img) == size(kern) - A = data(img) - krn = reflect(kern) - out = real(ifftshift(ifft(fft(A).*fft(krn)))) - elseif border != "inner" - prepad = [div(size(kern,i)-1, 2) for i = 1:N] - postpad = [div(size(kern,i), 2) for i = 1:N] - fullpad = Int[nextprod([2,3], size(img,i) + prepad[i] + postpad[i]) - size(img, i) - prepad[i] for i = 1:N] # work around julia #15276 - A = padarray(img, prepad, fullpad, border, convert(T, value)) - krn = zeros(eltype(one(T)*one(K)), size(A)) - indexesK = ntuple(d->[size(krn,d)-prepad[d]+1:size(krn,d);1:size(kern,d)-prepad[d]], N) - krn[indexesK...] = reflect(kern) - AF = ifft(fft(A).*fft(krn)) - out = Array(realtype(eltype(AF)), size(img)) - indexesA = ntuple(d->postpad[d]+1:size(img,d)+postpad[d], N) - copyreal!(out, AF, indexesA) - else - A = data(img) - prepad = [div(size(kern,i)-1, 2) for i = 1:N] - postpad = [div(size(kern,i), 2) for i = 1:N] - krn = zeros(eltype(one(T)*one(K)), size(A)) - indexesK = ntuple(d->[size(krn,d)-prepad[d]+1:size(krn,d);1:size(kern,d)-prepad[d]], N) - krn[indexesK...] = reflect(kern) - AF = ifft(fft(A).*fft(krn)) - out = Array(realtype(eltype(AF)), ([size(img)...] - prepad - postpad)...) - indexesA = ntuple(d->postpad[d]+1:size(img,d)-prepad[d], N) - copyreal!(out, AF, indexesA) - end - out -end - -# flips the dimension specified by name instead of index -# it is thus independent of the storage order -Base.flipdim(img::AbstractImage, dimname::String) = shareproperties(img, flipdim(data(img), dimindex(img, dimname))) - -flipx(img::AbstractImage) = flipdim(img, "x") -flipy(img::AbstractImage) = flipdim(img, "y") -flipz(img::AbstractImage) = flipdim(img, "z") - -# Generalization of rot180 -@generated function reflect{T,N}(A::AbstractArray{T,N}) - quote - B = Array(T, size(A)) - @nexprs $N d->(n_d = size(A, d)+1) - @nloops $N i A d->(j_d = n_d - i_d) begin - @nref($N, B, j) = @nref($N, A, i) - end - B - end -end - - -for N = 1:5 - @eval begin - function copyreal!{T<:Real}(dst::Array{T,$N}, src, I::Tuple{Vararg{UnitRange{Int}}}) - @nexprs $N d->(I_d = I[d]) - @nloops $N i dst d->(j_d = first(I_d)+i_d-1) begin - (@nref $N dst i) = real(@nref $N src j) - end - dst - end - function copyreal!{T<:Complex}(dst::Array{T,$N}, src, I::Tuple{Vararg{UnitRange{Int}}}) - @nexprs $N d->I_d = I[d] - @nloops $N i dst d->(j_d = first(I_d)+i_d-1) begin - (@nref $N dst i) = @nref $N src j - end - dst - end - end -end - -realtype{R<:Real}(::Type{R}) = R -realtype{R<:Real}(::Type{Complex{R}}) = R - - -# IIR filtering with Gaussians -# See -# Young, van Vliet, and van Ginkel, "Recursive Gabor Filtering", -# IEEE TRANSACTIONS ON SIGNAL PROCESSING, VOL. 50: 2798-2805. -# and -# Triggs and Sdika, "Boundary Conditions for Young - van Vliet -# Recursive Filtering, IEEE TRANSACTIONS ON SIGNAL PROCESSING, -# Here we're using NA boundary conditions, so we set i- and i+ -# (in Triggs & Sdika notation) to zero. -# Note these two papers use different sign conventions for the coefficients. - -# Note: astype is ignored for AbstractFloat input -""" -``` -imgf = imfilter_gaussian(img, sigma) -``` - -filters `img` with a Gaussian of the specified width. `sigma` should have -one value per array dimension (any number of dimensions are supported), 0 -indicating that no filtering is to occur along that dimension. Uses the Young, -van Vliet, and van Ginkel IIR-based algorithm to provide fast gaussian filtering -even with large `sigma`. Edges are handled by "NA" conditions, meaning the -result is normalized by the number and weighting of available pixels, and -missing data (NaNs) are handled likewise. -""" -function imfilter_gaussian{CT<:Colorant}(img::AbstractArray{CT}, sigma; emit_warning = true, astype::Type=Float64) - A = reinterpret(eltype(CT), data(img)) - newsigma = ndims(A) > ndims(img) ? [0;sigma] : sigma - ret = imfilter_gaussian(A, newsigma; emit_warning=emit_warning, astype=astype) - shareproperties(img, reinterpret(base_colorant_type(CT), ret)) -end - -function imfilter_gaussian{T<:AbstractFloat}(img::AbstractArray{T}, sigma::Vector; emit_warning = true, astype::Type=Float64) - if all(sigma .== 0) - return img - end - A = copy(data(img)) - nanflag = @compat isnan.(A) - hasnans = any(nanflag) - if hasnans - A[nanflag] = zero(T) - validpixels = convert(Array{T}, !nanflag) - imfilter_gaussian!(A, validpixels, sigma; emit_warning=emit_warning) - A[nanflag] = convert(T, NaN) - else - imfilter_gaussian_no_nans!(A, sigma; emit_warning=emit_warning) - end - shareproperties(img, A) -end - -# For these types, you can't have NaNs -function imfilter_gaussian{T<:Union{Integer,UFixed},TF<:AbstractFloat}(img::AbstractArray{T}, sigma::Vector; emit_warning = true, astype::Type{TF}=Float64) - A = copy!(Array(TF, size(img)), data(img)) - if all(sigma .== 0) - return shareproperties(img, A) - end - imfilter_gaussian_no_nans!(A, sigma; emit_warning=emit_warning) - shareproperties(img, A) -end - -# This version is in-place, and destructive -# Any NaNs have to already be removed from data (and marked in validpixels) -function imfilter_gaussian!{T<:AbstractFloat}(data::Array{T}, validpixels::Array{T}, sigma::Vector; emit_warning = true) - nd = ndims(data) - if length(sigma) != nd - error("Dimensionality mismatch") - end - _imfilter_gaussian!(data, sigma, emit_warning=emit_warning) - _imfilter_gaussian!(validpixels, sigma, emit_warning=false) - for i = 1:length(data) - data[i] /= validpixels[i] - end - return data -end - -# When there are no NaNs, the normalization is separable and hence can be computed far more efficiently -# This speeds the algorithm by approximately twofold -function imfilter_gaussian_no_nans!{T<:AbstractFloat}(data::Array{T}, sigma::Vector; emit_warning = true) - nd = ndims(data) - if length(sigma) != nd - error("Dimensionality mismatch") - end - _imfilter_gaussian!(data, sigma, emit_warning=emit_warning) - denom = Array(Vector{T}, nd) - for i = 1:nd - denom[i] = ones(T, size(data, i)) - if sigma[i] > 0 - _imfilter_gaussian!(denom[i], sigma[i:i], emit_warning=false) - end - end - imfgnormalize!(data, denom) - return data -end - -for N = 1:5 - @eval begin - function imfgnormalize!{T}(data::Array{T,$N}, denom) - @nextract $N denom denom - @nloops $N i data begin - den = one(T) - @nexprs $N d->(den *= denom_d[i_d]) - (@nref $N data i) /= den - end - end - end -end - -function iir_gaussian_coefficients(T::Type, sigma::Number; emit_warning::Bool = true) - if sigma < 1 && emit_warning - warn("sigma is too small for accuracy") - end - m0 = convert(T,1.16680) - m1 = convert(T,1.10783) - m2 = convert(T,1.40586) - q = convert(T,1.31564*(sqrt(1+0.490811*sigma*sigma) - 1)) - scale = (m0+q)*(m1*m1 + m2*m2 + 2m1*q + q*q) - B = m0*(m1*m1 + m2*m2)/scale - B *= B - # This is what Young et al call b, but in filt() notation would be called a - a1 = q*(2*m0*m1 + m1*m1 + m2*m2 + (2*m0+4*m1)*q + 3*q*q)/scale - a2 = -q*q*(m0 + 2m1 + 3q)/scale - a3 = q*q*q/scale - a = [-a1,-a2,-a3] - Mdenom = (1+a1-a2+a3)*(1-a1-a2-a3)*(1+a2+(a1-a3)*a3) - M = [-a3*a1+1-a3^2-a2 (a3+a1)*(a2+a3*a1) a3*(a1+a3*a2); - a1+a3*a2 -(a2-1)*(a2+a3*a1) -(a3*a1+a3^2+a2-1)*a3; - a3*a1+a2+a1^2-a2^2 a1*a2+a3*a2^2-a1*a3^2-a3^3-a3*a2+a3 a3*(a1+a3*a2)]/Mdenom; - return a, B, M -end - -function _imfilter_gaussian!{T<:AbstractFloat}(A::Array{T}, sigma::Vector; emit_warning::Bool = true) - nd = ndims(A) - szA = [size(A,i) for i = 1:nd] - strdsA = [stride(A,i) for i = 1:nd] - for d = 1:nd - if sigma[d] == 0 - continue - end - if size(A, d) < 3 - error("All filtered dimensions must be of size 3 or larger") - end - a, B, M = iir_gaussian_coefficients(T, sigma[d], emit_warning=emit_warning) - a1 = a[1] - a2 = a[2] - a3 = a[3] - n1 = size(A,1) - keepdims = [false;trues(nd-1)] - if d == 1 - x = zeros(T, 3) - vstart = zeros(T, 3) - szhat = szA[keepdims] - strdshat = strdsA[keepdims] - if isempty(szhat) - szhat = [1] - strdshat = [1] - end - @inbounds @forcartesian c szhat begin - coloffset = offset(c, strdshat) - A[2+coloffset] -= a1*A[1+coloffset] - A[3+coloffset] -= a1*A[2+coloffset] + a2*A[1+coloffset] - for i = 4+coloffset:n1+coloffset - A[i] -= a1*A[i-1] + a2*A[i-2] + a3*A[i-3] - end - copytail!(x, A, coloffset, 1, n1) - A_mul_B!(vstart, M, x) - A[n1+coloffset] = vstart[1] - A[n1-1+coloffset] -= a1*vstart[1] + a2*vstart[2] + a3*vstart[3] - A[n1-2+coloffset] -= a1*A[n1-1+coloffset] + a2*vstart[1] + a3*vstart[2] - for i = n1-3+coloffset:-1:1+coloffset - A[i] -= a1*A[i+1] + a2*A[i+2] + a3*A[i+3] - end - end - else - x = Array(T, 3, n1) - vstart = similar(x) - keepdims[d] = false - szhat = szA[keepdims] - szd = szA[d] - strdshat = strdsA[keepdims] - strdd = strdsA[d] - if isempty(szhat) - szhat = [1] - strdshat = [1] - end - @inbounds @forcartesian c szhat begin - coloffset = offset(c, strdshat) # offset for the remaining dimensions - for i = 1:n1 A[i+strdd+coloffset] -= a1*A[i+coloffset] end - for i = 1:n1 A[i+2strdd+coloffset] -= a1*A[i+strdd+coloffset] + a2*A[i+coloffset] end - for j = 3:szd-1 - jj = j*strdd+coloffset - for i = jj+1:jj+n1 A[i] -= a1*A[i-strdd] + a2*A[i-2strdd] + a3*A[i-3strdd] end - end - copytail!(x, A, coloffset, strdd, szd) - A_mul_B!(vstart, M, x) - for i = 1:n1 A[i+(szd-1)*strdd+coloffset] = vstart[1,i] end - for i = 1:n1 A[i+(szd-2)*strdd+coloffset] -= a1*vstart[1,i] + a2*vstart[2,i] + a3*vstart[3,i] end - for i = 1:n1 A[i+(szd-3)*strdd+coloffset] -= a1*A[i+(szd-2)*strdd+coloffset] + a2*vstart[1,i] + a3*vstart[2,i] end - for j = szd-4:-1:0 - jj = j*strdd+coloffset - for i = jj+1:jj+n1 A[i] -= a1*A[i+strdd] + a2*A[i+2strdd] + a3*A[i+3strdd] end - end - end - end - @inbounds for i = 1:length(A) - A[i] *= B - end - end - A -end - -function offset(c::Vector{Int}, strds::Vector{Int}) - o = 0 - for i = 1:length(c) - o += (c[i]-1)*strds[i] - end - o -end - -function copytail!(dest, A, coloffset, strd, len) - for j = 1:3 - for i = 1:size(dest, 2) - tmp = A[i + coloffset + (len-j)*strd] - dest[j,i] = tmp - end - end - dest -end - -@generated function blob_LoG_helper1{T,N}(img_LoG::AbstractArray{T,N}, - radii, x) - :(img_LoG[x...], radii[x[1]], (@ntuple $(N - 1) d->x[d+1])...) -end - -function blob_LoG_helper2(img_LoG, radii, maxima) - return [blob_LoG_helper1(img_LoG, radii, x) for x in maxima] -end - -""" -`blob_LoG(img, sigmas) -> Vector{Tuple}` + blob_LoG(img, sigmas) -> Vector{Tuple} Find "blobs" in an N-D image using Lapacian of Gaussians at the specifed sigmas. Returned are the local maxima's heights, radii, and spatial coordinates. @@ -1062,40 +368,39 @@ International Journal of Computer Vision, 30(2), 79–116. Note that only 2-D images are currently supported due to a limitation of `imfilter_LoG`. """ -@generated function blob_LoG{T,N}(img::AbstractArray{T,N}, sigmas) - quote - img_LoG = Array(Float64, length(sigmas), size(img)...) - @inbounds for isigma in eachindex(sigmas) - img_LoG[isigma,:] = sigmas[isigma] * imfilter_LoG(img, sigmas[isigma]) - end - - radii = sqrt(2.0)*sigmas - maxima = findlocalmaxima(img_LoG, 1:ndims(img_LoG), (true, falses(N)...)) - blob_LoG_helper2(img_LoG, radii, maxima) +function blob_LoG{T,N}(img::AbstractArray{T,N}, sigmas) + img_LoG = Array(Float64, length(sigmas), size(img)...) + @inbounds for isigma in eachindex(sigmas) + img_LoG[isigma,:] = sigmas[isigma] * imfilter_LoG(img, sigmas[isigma]) end + + radii = sqrt(2.0)*sigmas + maxima = findlocalmaxima(img_LoG, 1:ndims(img_LoG), (true, falses(N)...)) + [(img_LoG[x...], radii[x[1]], tail(x.I)...) for x in maxima] end findlocalextrema{T,N}(img::AbstractArray{T,N}, region, edges::Bool, order) = findlocalextrema(img, region, ntuple(d->edges,N), order) -@generated function findlocalextrema{T,N}(img::AbstractArray{T,N}, region::Union{Tuple{Int},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) - quote - issubset(region,1:ndims(img)) || throw(ArgumentError("Invalid region.")) - extrema = Tuple{(@ntuple $N d->Int)...}[] - @inbounds @nloops $N i d->((1+!edges[d]):(size(img,d)-!edges[d])) begin - isextrema = true - img_I = (@nref $N img i) - @nloops $N j d->(in(d,region) ? (max(1,i_d-1):min(size(img,d),i_d+1)) : i_d) begin - (@nall $N d->(j_d == i_d)) && continue - if !Base.Order.lt(order, (@nref $N img j), img_I) - isextrema = false - break - end - end - isextrema && push!(extrema, (@ntuple $N d->(i_d))) - end - extrema - end -end +# FIXME +# @generated function findlocalextrema{T,N}(img::AbstractArray{T,N}, region::Union{Tuple{Int},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) +# quote +# issubset(region,1:ndims(img)) || throw(ArgumentError("Invalid region.")) +# extrema = Tuple{(@ntuple $N d->Int)...}[] +# @inbounds @nloops $N i d->((1+!edges[d]):(size(img,d)-!edges[d])) begin +# isextrema = true +# img_I = (@nref $N img i) +# @nloops $N j d->(in(d,region) ? (max(1,i_d-1):min(size(img,d),i_d+1)) : i_d) begin +# (@nall $N d->(j_d == i_d)) && continue +# if !Base.Order.lt(order, (@nref $N img j), img_I) +# isextrema = false +# break +# end +# end +# isextrema && push!(extrema, (@ntuple $N d->(i_d))) +# end +# extrema +# end +# end """ `findlocalmaxima(img, [region, edges]) -> Vector{Tuple}` @@ -1115,103 +420,86 @@ Like `findlocalmaxima`, but returns the coordinates of the smallest elements. findlocalminima(img::AbstractArray, region=coords_spatial(img), edges=true) = findlocalextrema(img, region, edges, Base.Order.Reverse) +# FIXME # Laplacian of Gaussian filter # Separable implementation from Huertas and Medioni, # IEEE Trans. Pat. Anal. Mach. Int., PAMI-8, 651, (1986) -""" -``` -imgf = imfilter_LoG(img, sigma, [border]) -``` - -filters a 2D image with a Laplacian of Gaussian of the specified width. `sigma` -may be a vector with one value per array dimension, or may be a single scalar -value for uniform filtering in both dimensions. Uses the Huertas and Medioni -separable algorithm. -""" -function imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Vector, border="replicate") - # Limited to 2D for now. - # See Sage D, Neumann F, Hediger F, Gasser S, Unser M. - # Image Processing, IEEE Transactions on. (2005) 14(9):1372-1383. - # for 3D. - - # Set up 1D kernels - @assert length(σ) == 2 - σx, σy = σ[1], σ[2] - h1(ξ, σ) = sqrt(1/(2π*σ^4))*(1 - ξ^2/σ^2)exp(-ξ^2/(2σ^2)) - h2(ξ, σ) = sqrt(1/(2π*σ^4))*exp(-ξ^2/(2σ^2)) - - w = 8.5σx - kh1x = Float64[h1(i, σx) for i = -floor(w/2):floor(w/2)] - kh2x = Float64[h2(i, σx) for i = -floor(w/2):floor(w/2)] - - w = 8.5σy - kh1y = Float64[h1(i, σy) for i = -floor(w/2):floor(w/2)] - kh2y = Float64[h2(i, σy) for i = -floor(w/2):floor(w/2)] - - # Set up padding index lists - kernlenx = length(kh1x) - prepad = div(kernlenx - 1, 2) - postpad = div(kernlenx, 2) - Ix = padindexes(img, 1, prepad, postpad, border) - - kernleny = length(kh1y) - prepad = div(kernleny - 1, 2) - postpad = div(kernleny, 2) - Iy = padindexes(img, 2, prepad, postpad, border) - - sz = size(img) - # Store intermediate result in a transposed array - # Allows column-major second stage filtering - img1 = Array(Float64, (sz[2], sz[1])) - img2 = Array(Float64, (sz[2], sz[1])) - for j in 1:sz[2] - for i in 1:sz[1] - tmp1, tmp2 = 0.0, 0.0 - for k in 1:kernlenx - @inbounds tmp1 += kh1x[k] * img[Ix[i + k - 1], j] - @inbounds tmp2 += kh2x[k] * img[Ix[i + k - 1], j] - end - @inbounds img1[j, i] = tmp1 # Note the transpose - @inbounds img2[j, i] = tmp2 - end - end - - img12 = Array(Float64, sz) # Original image dims here - img21 = Array(Float64, sz) - for j in 1:sz[1] - for i in 1:sz[2] - tmp12, tmp21 = 0.0, 0.0 - for k in 1:kernleny - @inbounds tmp12 += kh2y[k] * img1[Iy[i + k - 1], j] - @inbounds tmp21 += kh1y[k] * img2[Iy[i + k - 1], j] - end - @inbounds img12[j, i] = tmp12 # Transpose back to original dims - @inbounds img21[j, i] = tmp21 - end - end - copyproperties(img, img12 + img21) -end - -imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Real, border="replicate") = - imfilter_LoG(img::AbstractArray{T,2}, [σ, σ], border) - -function padindexes{T,n}(img::AbstractArray{T,n}, dim, prepad, postpad, border::AbstractString) - M = size(img, dim) - I = Array(Int, M + prepad + postpad) - I = [(1 - prepad):(M + postpad);] - @compat if border == "replicate" - I = min.(max.(I, 1), M) - elseif border == "circular" - I = 1 .+ mod.(I .- 1, M) - elseif border == "symmetric" - I = [1:M; M:-1:1][1 .+ mod.(I .- 1, 2 * M)] - elseif border == "reflect" - I = [1:M; M-1:-1:2][1 .+ mod.(I .- 1, 2 * M - 2)] - else - error("unknown border condition") - end - I -end +# """ +# ``` +# imgf = imfilter_LoG(img, sigma, [border]) +# ``` + +# filters a 2D image with a Laplacian of Gaussian of the specified width. `sigma` +# may be a vector with one value per array dimension, or may be a single scalar +# value for uniform filtering in both dimensions. Uses the Huertas and Medioni +# separable algorithm. +# """ +# function imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Vector, border="replicate") +# # Limited to 2D for now. +# # See Sage D, Neumann F, Hediger F, Gasser S, Unser M. +# # Image Processing, IEEE Transactions on. (2005) 14(9):1372-1383. +# # for 3D. + +# # Set up 1D kernels +# @assert length(σ) == 2 +# σx, σy = σ[1], σ[2] +# h1(ξ, σ) = sqrt(1/(2π*σ^4))*(1 - ξ^2/σ^2)exp(-ξ^2/(2σ^2)) +# h2(ξ, σ) = sqrt(1/(2π*σ^4))*exp(-ξ^2/(2σ^2)) + +# w = 8.5σx +# kh1x = Float64[h1(i, σx) for i = -floor(w/2):floor(w/2)] +# kh2x = Float64[h2(i, σx) for i = -floor(w/2):floor(w/2)] + +# w = 8.5σy +# kh1y = Float64[h1(i, σy) for i = -floor(w/2):floor(w/2)] +# kh2y = Float64[h2(i, σy) for i = -floor(w/2):floor(w/2)] + +# # Set up padding index lists +# kernlenx = length(kh1x) +# prepad = div(kernlenx - 1, 2) +# postpad = div(kernlenx, 2) +# Ix = padindexes(img, 1, prepad, postpad, border) + +# kernleny = length(kh1y) +# prepad = div(kernleny - 1, 2) +# postpad = div(kernleny, 2) +# Iy = padindexes(img, 2, prepad, postpad, border) + +# sz = size(img) +# # Store intermediate result in a transposed array +# # Allows column-major second stage filtering +# img1 = Array(Float64, (sz[2], sz[1])) +# img2 = Array(Float64, (sz[2], sz[1])) +# for j in 1:sz[2] +# for i in 1:sz[1] +# tmp1, tmp2 = 0.0, 0.0 +# for k in 1:kernlenx +# @inbounds tmp1 += kh1x[k] * img[Ix[i + k - 1], j] +# @inbounds tmp2 += kh2x[k] * img[Ix[i + k - 1], j] +# end +# @inbounds img1[j, i] = tmp1 # Note the transpose +# @inbounds img2[j, i] = tmp2 +# end +# end + +# img12 = Array(Float64, sz) # Original image dims here +# img21 = Array(Float64, sz) +# for j in 1:sz[1] +# for i in 1:sz[2] +# tmp12, tmp21 = 0.0, 0.0 +# for k in 1:kernleny +# @inbounds tmp12 += kh2y[k] * img1[Iy[i + k - 1], j] +# @inbounds tmp21 += kh1y[k] * img2[Iy[i + k - 1], j] +# end +# @inbounds img12[j, i] = tmp12 # Transpose back to original dims +# @inbounds img21[j, i] = tmp21 +# end +# end +# copyproperties(img, img12 + img21) +# end + +# imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Real, border="replicate") = +# imfilter_LoG(img::AbstractArray{T,2}, [σ, σ], border) ### restrict, for reducing the image size by 2-fold # This properly anti-aliases. The only "oddity" is that edges tend towards zero under @@ -1504,30 +792,36 @@ erode(img::AbstractArray, region=coords_spatial(img)) = erode!(copy(img), region dilate!(maxfilt, region=coords_spatial(maxfilt)) = extremefilt!(data(maxfilt), Base.Order.Forward, region) erode!(minfilt, region=coords_spatial(minfilt)) = extremefilt!(data(minfilt), Base.Order.Reverse, region) -function extremefilt!(extrfilt::AbstractArray, order::Ordering, region=coords_spatial(extrfilt)) - for d = 1:ndims(extrfilt) - if size(extrfilt, d) == 1 || !in(d, region) +function extremefilt!(A::AbstractArray, select::Function, region=coords_spatial(A)) + inds = indices(A) + for d = 1:ndims(A) + if size(A, d) == 1 || !in(d, region) continue end - sz = [size(extrfilt,i) for i = 1:ndims(extrfilt)] - s = stride(extrfilt, d) - sz[d] = 1 - @forcartesian i sz begin - k = cartesian_linear(extrfilt, i) - a2 = extrfilt[k] - a3 = extrfilt[k+s] - extrfilt[k] = extr(order, a2, a3) - for l = 2:size(extrfilt,d)-1 - k += s - a1 = a2 - a2 = a3 - a3 = extrfilt[k+s] - extrfilt[k] = extr(order, a1, a2, a3) - end - extrfilt[k+s] = extr(order, a2, a3) + Rpre = CartesianRange(inds[1:d-1]) + Rpost = CartesianRange(inds[d+1:end]) + _extremefilt!(A, select, Rpre, inds[d], Rpost) + end + A +end + +@noinline function _extremefilt!(A, select, Rpre, inds, Rpost) + # This is not cache efficient, unfortunately + for Ipost in Rpost, Ipre in Rpre + # first element along dim + i1 = first(inds) + a2, a3 = A[Ipre, i1, Ipost], A[Ipre, i1+1, Ipost] + A[Ipre, i1, Ipost] = select(a2, a3) + # interior along dim + for i = i1+2:last(inds)-1 + a1, a2 = a2, a3 + a3 = A[Ipre, i, Ipost] + A[Ipre, i, Ipost] = select(select(a1, a2), a3) end + # last element along dim + A[Ipre, last(inds), Ipost] = select(a2, a3) end - extrfilt + A end """ @@ -1584,196 +878,6 @@ extr(order::Ordering, x::Color, y::Color) = extr(order, convert(RGB, x), convert extr(order::Ordering, x::Color, y::Color, z::Color) = extr(order, convert(RGB, x), convert(RGB, y), convert(RGB, z)) - -# Min max filter - -# This is a port of the Lemire min max filter as implemented by Bruno Luong -# http://arxiv.org/abs/cs.DS/0610046 -# http://lemire.me/ -# http://www.mathworks.com/matlabcentral/fileexchange/24705-min-max-filter - -type Wedge{A <: AbstractArray} - buffer::A - size::Int - n::Int - first::Int - last::Int - mxn::Int -end - - -for N = 2:4 - @eval begin - function extrema_filter{T <: Number}(A::Array{T, $N}, window::Array{Int, 1}) - - maxval_temp = copy(A); minval_temp = copy(A) - - for dim = 1:$N - - # For all but the last dimension - @nloops $(N-1) i maxval_temp begin - - # Create index for full array (fa) length - @nexprs $(N) j->(fa_{j} = 1:size(maxval_temp)[j]) - @nexprs $(N-1) j->(fa_{j} = i_j) - - # Create index for short array (sa) length - @nexprs $(N) j->(sa_{j} = 1:size(maxval_temp)[j] - window[dim] + 1) - @nexprs $(N-1) j->(sa_{j} = i_j) - - # Filter the last dimension - (@nref $N minval_temp sa) = min_filter(vec( @nref $N minval_temp fa), window[dim]) - (@nref $N maxval_temp sa) = max_filter(vec( @nref $N maxval_temp fa), window[dim]) - - end - - # Circular shift the dimensions - perm_idx = @compat mod.(1:$N, $N) .+ 1 - maxval_temp = permutedims(maxval_temp, perm_idx) - minval_temp = permutedims(minval_temp, perm_idx) - - end - - # The dimensions to extract - @nexprs $N j->(a_{j} = 1:size(A, j)-window[j]+1) - - # Extract set dimensions - maxval_out = @nref $N maxval_temp a - minval_out = @nref $N minval_temp a - - return minval_out, maxval_out - end - end -end - - -function extrema_filter{T <: Number}(a::AbstractArray{T}, window::Int) - - n = length(a) - - # Initialise the output variables - # This is the running minimum and maximum over the specified window length - minval = zeros(T, 1, n-window+1) - maxval = zeros(T, 1, n-window+1) - - # Initialise the internal wedges - # U[1], L[1] are the location of the global maximum and minimum - # U[2], L[2] are the maximum and minimum over (U1, inf) - L = Wedge(zeros(Int,1,window+1), window+1, 0, 1, 0, 0) # Min - U = Wedge(zeros(Int,1,window+1), window+1, 0, 1, 0, 0) - - for i = 2:n - if i > window - if !wedgeisempty(U) - maxval[i-window] = a[getfirst(U)] - else - maxval[i-window] = a[i-1] - end - if !wedgeisempty(L) - minval[i-window] = a[getfirst(L)] - else - minval[i-window] = a[i-1] - end - end # window - - if a[i] > a[i-1] - pushback!(L, i-1) - if i==window+getfirst(L); L=popfront(L); end - while !wedgeisempty(U) - if a[i] <= a[getlast(U)] - if i == window+getfirst(U); U = popfront(U); end - break - end - U = popback(U) - end - - else - - pushback!(U, i-1) - if i==window+getfirst(U); U=popfront(U); end - - while !wedgeisempty(L) - if a[i] >= a[getlast(L)] - if i == window+getfirst(L); L = popfront(L); end - break - end - L = popback(L) - end - - end # a>a-1 - - end # for i - - i = n+1 - if !wedgeisempty(U) - maxval[i-window] = a[getfirst(U)] - else - maxval[i-window] = a[i-1] - end - - if !wedgeisempty(L) - minval[i-window] = a[getfirst(L)] - else - minval[i-window] = a[i-1] - end - - return minval, maxval -end - - -function min_filter(a::AbstractArray, window::Int) - - minval, maxval = extrema_filter(a, window) - - return minval -end - - -function max_filter(a::AbstractArray, window::Int) - - minval, maxval = extrema_filter(a, window) - - return maxval -end - - -function wedgeisempty(X::Wedge) - X.n <= 0 -end - -function pushback!(X::Wedge, v) - X.last += 1 - if X.last > X.size - X.last = 1 - end - X.buffer[X.last] = v - X.n = X.n+1 - X.mxn = max(X.mxn, X.n) -end - -function getfirst(X::Wedge) - X.buffer[X.first] -end - -function getlast(X::Wedge) - X.buffer[X.last] -end - -function popfront(X::Wedge) - X.n = X.n-1 - X.first = mod(X.first, X.size) + 1 - return X -end - -function popback(X::Wedge) - X.n = X.n-1 - X.last = mod(X.last-2, X.size) + 1 - return X -end - - - - # phantom images """ diff --git a/src/edge.jl b/src/edge.jl index 0953854e..38b53867 100644 --- a/src/edge.jl +++ b/src/edge.jl @@ -1,150 +1,5 @@ ### Edge and Gradient Related Image Operations ### -# Edge/gradient filters - -"`kern1, kern2 = sobel()` returns Sobel filters for dimensions 1 and 2 of your image" -function sobel() - f = [ -1.0 0.0 1.0 - -2.0 0.0 2.0 - -1.0 0.0 1.0 ] - return f', f -end - -"`kern1, kern2 = prewitt()` returns Prewitt filters for dimensions 1 and 2 of your image" -function prewitt() - f = [ -1.0 0.0 1.0 - -1.0 0.0 1.0 - -1.0 0.0 1.0 ] - return f', f -end - -# Consistent Gradient Operators -# Ando Shigeru -# IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, March 2000 -# -# TODO: These coefficients were taken from the paper It would be nice -# to resolve the optimization problem and use higher precision -# versions, which might allow better separable approximations of -# ando4 and ando5. - -""" -`kern1, kern2 = ando3()` returns optimal 3x3 filters for dimensions 1 and 2 of your image, as defined in -Ando Shigeru, IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, March 2000. - -See also: `ando4`, `ando5`. -""" -function ando3() - f = [ -0.112737 0.0 0.112737 - -0.274526 0.0 0.274526 - -0.112737 0.0 0.112737 ] - return f', f -end - -# Below, the ando4() and ando5() functions return filters with -# the published filter values. The ando4_sep() and ando5_sep() -# functions return separable approximations to the corresponding -# filters, estimated using the projection of the actual values on the -# eigenvector corresponding to the largest eigenvalue of the SVD of -# the original filter. - -""" -`kern1, kern2 = ando4()` returns optimal 4x4 filters for dimensions 1 and 2 of your image, as defined in -Ando Shigeru, IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, March 2000. - -See also: `ando4_sep`, `ando3`, `ando5`. -""" -function ando4() - f = [ -0.022116 -0.025526 0.025526 0.022116 - -0.098381 -0.112984 0.112984 0.098381 - -0.098381 -0.112984 0.112984 0.098381 - -0.022116 -0.025526 0.025526 0.022116 ] - return f', f -end - -""" -`kern1, kern2 = ando4_sep()` returns separable approximations of the -optimal 4x4 filters for dimensions 1 and 2 of your image, as defined -in Ando Shigeru, IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, -March 2000. - -See also: `ando4`. -""" -function ando4_sep() - f = [-0.022175974729759376 -0.025473821998749126 0.025473821998749126 0.022175974729759376 - -0.09836750569692418 -0.11299599504060115 0.11299599504060115 0.09836750569692418 - -0.09836750569692418 -0.11299599504060115 0.11299599504060115 0.09836750569692418 - -0.022175974729759376 -0.025473821998749126 0.025473821998749126 0.022175974729759376] - return f', f -end - -""" -`kern1, kern2 = ando5()` returns optimal 5x5 filters for dimensions 1 and 2 of your image, as defined in -Ando Shigeru, IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, March 2000. - -See also: `ando5_sep`, `ando3`, `ando4`. -""" -function ando5() - f = [ -0.003776 -0.010199 0.0 0.010199 0.003776 - -0.026786 -0.070844 0.0 0.070844 0.026786 - -0.046548 -0.122572 0.0 0.122572 0.046548 - -0.026786 -0.070844 0.0 0.070844 0.026786 - -0.003776 -0.010199 0.0 0.010199 0.003776 ] - return f', f -end - -""" -`kern1, kern2 = ando5_sep()` returns separable approximations of the -optimal 5x5 filters for dimensions 1 and 2 of your image, as defined -in Ando Shigeru, IEEE Trans. Pat. Anal. Mach. Int., vol. 22 no 3, -March 2000. - -See also: `ando5`. -""" -function ando5_sep() - f = [-0.0038543900766123762 -0.0101692999709622 0.0 0.0101692999709622 0.0038543900766123762 - -0.026843218687756566 -0.07082229291692607 0.0 0.07082229291692607 0.026843218687756566 - -0.046468878396946627 -0.12260200818803602 0.0 0.12260200818803602 0.046468878396946627 - -0.026843218687756566 -0.07082229291692607 0.0 0.07082229291692607 0.026843218687756566 - -0.0038543900766123762 -0.0101692999709622 0.0 0.0101692999709622 0.0038543900766123762] - return f', f -end - -# Image gradients in the X and Y direction -""" -``` -grad_x, grad_y = imgradients(img, [method], [border]) -``` - -performs edge-detection filtering. `method` is one of `"sobel"`, `"prewitt"`, `"ando3"`, -`"ando4"`, `"ando4_sep"`, `"ando5"`, or `"ando5_sep"`, defaulting to `"ando3"` -(see the functions of the same name for more information). `border` is any of -the boundary conditions specified in `padarray`. - -Returns a tuple containing `x` (horizontal) and `y` (vertical) gradient images -of the same size as `img`, calculated using the requested method and border. -""" -function imgradients(img::AbstractArray, method::AbstractString="ando3", border::AbstractString="replicate") - sx,sy = spatialorder(img)[1] == "x" ? (1,2) : (2,1) - s = (method == "sobel" ? sobel() : - method == "prewitt" ? prewitt() : - method == "ando3" ? ando3() : - method == "ando4" ? ando4() : - method == "ando5" ? ando5() : - method == "ando4_sep" ? ando4_sep() : - method == "ando5_sep" ? ando5_sep() : - error("Unknown gradient method: $method")) - - grad_x = imfilter(img, s[sx], border) - grad_y = imfilter(img, s[sy], border) - - return grad_x, grad_y -end - -function imgradients{T<:Color}(img::AbstractArray{T}, method::AbstractString="ando3", border::AbstractString="replicate") - # Remove Color information - imgradients(reinterpret(eltype(eltype(img)), img), method, border) -end - # Magnitude of gradient, calculated from X and Y image gradients """ ``` diff --git a/src/map.jl b/src/map.jl index 47d0db3d..fcc5f46a 100644 --- a/src/map.jl +++ b/src/map.jl @@ -72,8 +72,8 @@ map1(mapi::Union{MapNone{RGB24}, MapNone{ARGB32}}, b::Bool) = ifelse(b, 0xffuf8, map1(mapi::Union{MapNone{RGB24},MapNone{ARGB32}}, val::Fractional) = convert(UFixed8, val) map1{CT<:Colorant}(mapi::MapNone{CT}, val::Fractional) = convert(eltype(CT), val) -immap{T<:Colorant}(mapi::MapNone{T}, img::AbstractImageIndexed{T}) = convert(Image{T}, img) -immap{C<:Colorant}(mapi::MapNone{C}, img::AbstractImageDirect{C}) = img # ambiguity resolution +# immap{T<:Colorant}(mapi::MapNone{T}, img::AbstractImageIndexed{T}) = convert(Image{T}, img) +# immap{C<:Colorant}(mapi::MapNone{C}, img::AbstractImageDirect{C}) = img # ambiguity resolution immap{T}(mapi::MapNone{T}, img::AbstractArray{T}) = img immap(::MapNone{UInt32}, val::RGB24) = val.color @@ -465,21 +465,21 @@ function immap{T}(mapi::MapInfo{T}, img::AbstractArray) map!(mapi, out, img) end -immap{C<:Colorant,R<:Real}(mapi::MapNone{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img) # ambiguity resolution -immap{C<:Colorant,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img) -function mapcd{C<:Colorant,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R}) - # For this case we have to check whether color is defined along an array axis - cd = colordim(img) - if cd > 0 - dims = setdiff(1:ndims(img), cd) - out = similar(img, C, size(img)[dims]) - map!(mapi, out, img, TypeConst{cd}) - else - out = similar(img, C) - map!(mapi, out, img) - end - out # note this isn't type-stable -end +# immap{C<:Colorant,R<:Real}(mapi::MapNone{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img) # ambiguity resolution +# immap{C<:Colorant,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img) +# function mapcd{C<:Colorant,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R}) +# # For this case we have to check whether color is defined along an array axis +# cd = colordim(img) +# if cd > 0 +# dims = setdiff(1:ndims(img), cd) +# out = similar(img, C, size(img)[dims]) +# map!(mapi, out, img, Val{cd}) +# else +# out = similar(img, C) +# map!(mapi, out, img) +# end +# out # note this isn't type-stable +# end function immap{T<:Colorant}(mapi::MapInfo{T}, img::AbstractImageIndexed) out = Image(Array(T, size(img)), properties(img)) @@ -521,31 +521,6 @@ function _mapindx!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::Abstr out end -# For when color is encoded along dimension CD -# NC is the number of color channels -# This is a very flexible implementation: color can be stored along any dimension, and it handles conversions to -# many different colorspace representations. -for (CT, NC) in ((Union{AbstractRGB,RGB24}, 3), (Union{RGBA,ARGB,ARGB32}, 4), (Union{AGray,GrayA,AGray32}, 2)) - for N = 1:4 - N1 = N+1 - @eval begin -function map!{T<:$CT,T1,T2,CD}(mapi::MapInfo{T}, out::AbstractArray{T1,$N}, img::AbstractArray{T2,$N1}, ::Type{TypeConst{CD}}) - mi = take(mapi, img) - dimg = data(img) - dout = data(out) - # Set up the index along the color axis - # We really only need dimension CD, but this will suffice - @nexprs $NC k->(@nexprs $N1 d->(j_k_d = k)) - # Loop over all the elements in the output, performing the conversion on each color component - @nloops $N i dout d->(d(j_k_d = i_d)) : (@nexprs $NC k->(j_k_{d+1} = i_d))) begin - @inbounds @nref($N, dout, i) = @ncall $NC T k->(map1(mi, @nref($N1, dimg, j_k))) - end - out -end - end - end -end - #### MapInfo defaults # Each "client" can define its own methods. "clients" include UFixed, @@ -680,11 +655,11 @@ uint32color!(buf, img::AbstractArray, mi::MapInfo) = map!(mi, buf, img) uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N}) = map!(mapinfo(UInt32, img), buf, img) uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N1}) = - map!(mapinfo(UInt32, img), buf, img, TypeConst{colordim(img)}) + map!(mapinfo(UInt32, img), buf, img, Val{colordim(img)}) uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N}, mi::MapInfo) = map!(mi, buf, img) uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N1}, mi::MapInfo) = - map!(mi, buf, img, TypeConst{colordim(img)}) + map!(mi, buf, img, Val{colordim(img)}) """ ``` @@ -697,23 +672,5 @@ Applies default or specified `ScaleMinMax` mapping to the image. sc(img::AbstractArray) = immap(ScaleMinMax(UFixed8, img), img) sc(img::AbstractArray, mn::Real, mx::Real) = immap(ScaleMinMax(UFixed8, img, mn, mx), img) -for (fn,T) in ((:float32, Float32), (:float64, Float64), (:ufixed8, UFixed8), - (:ufixed10, UFixed10), (:ufixed12, UFixed12), (:ufixed14, UFixed14), - (:ufixed16, UFixed16)) - @eval begin - function $fn{C<:Colorant}(A::AbstractArray{C}) - newC = eval(C.name.name){$T} - convert(Array{newC}, A) - end - $fn{C<:Colorant}(img::AbstractImage{C}) = shareproperties(img, $fn(data(img))) - end - if VERSION >= v"0.5.0" - @eval begin - $fn(x::Number) = convert($T, x) - $fn(str::AbstractString) = parse($T, str) - end - end -end - ufixedsc{T<:UFixed}(::Type{T}, img::AbstractImageDirect) = immap(mapinfo(T, img), img) ufixed8sc(img::AbstractImageDirect) = ufixedsc(UFixed8, img) From 9b171c1ec6739356603eec2dd3b3aa466ebffba0 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Sep 2016 08:32:28 -0500 Subject: [PATCH 02/38] Move old tests to test/old and get "core" tests so they complete (with failures) --- NEWS.md | 75 +++++++++++++++++ src/Images.jl | 1 + src/deprecated.jl | 6 ++ test/{ => old}/algorithms.jl | 0 test/{ => old}/core.jl | 152 ++++++++--------------------------- test/{ => old}/corner.jl | 0 test/{ => old}/distances.jl | 0 test/{ => old}/edge.jl | 0 test/{ => old}/exposure.jl | 0 test/{ => old}/map.jl | 0 test/{ => old}/overlays.jl | 0 test/{ => old}/parallel.jl | 0 test/{ => old}/restrict.jl | 0 test/old/runtests.jl | 26 ++++++ test/{ => old}/writemime.jl | 0 test/runtests.jl | 28 +------ 16 files changed, 144 insertions(+), 144 deletions(-) create mode 100644 NEWS.md create mode 100644 src/deprecated.jl rename test/{ => old}/algorithms.jl (100%) rename test/{ => old}/core.jl (74%) rename test/{ => old}/corner.jl (100%) rename test/{ => old}/distances.jl (100%) rename test/{ => old}/edge.jl (100%) rename test/{ => old}/exposure.jl (100%) rename test/{ => old}/map.jl (100%) rename test/{ => old}/overlays.jl (100%) rename test/{ => old}/parallel.jl (100%) rename test/{ => old}/restrict.jl (100%) create mode 100644 test/old/runtests.jl rename test/{ => old}/writemime.jl (100%) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 00000000..71afe67d --- /dev/null +++ b/NEWS.md @@ -0,0 +1,75 @@ +# v0.6 + +Images has been rewritten essentially from scratch for this +release. The major goals of the release are: + +- More consistent treatment of spatial orientation +- Stop losing key properties upon indexing +- More sensible treatment of indexed (colormap) images +- Better support for a wider range of array types +- Improvements in type-stability of many operations +- Improvements in the user experience through easier interfaces, more + informative error messages, and improved printing/display +- Improvements in documentation +- For users of former releases of Images, as smooth an upgrade path as + can be practically provided, through deprecations or informative + error messages + +Key changes (of which many are breaking): + +- Many properties that were formerly in the dictionary (colorspace, + spatial orientation, pixel spacing, and the presence/absence of a + time dimension) are now encoded by the type system. The `ImageAxes` + package (a small extension of `AxisArrays`) is now used for several + of these properties. This fixes the former loss of information like + spatial orientation when images were indexed (`img[5:20, 5:20])`. + +- The `Image` type (an array + dictionary) has been renamed + `ImageMeta`, and should be much less needed now that most properties + can be encoded with `AxisArrays`. `ImageMeta` is still useful if you + need to encode information like date/time at which the image was + taken, sky coordinates, patient IDs, or experimental conditions. + +- Full commitment to the use of `Colorant` types (as defined by the + `ColorTypes` and `Colors` packages) for encoding color + images. Arrays are no longer allowed to declare that they use one + axis (dimension) to store color information, i.e., a `m×n×3 Float32` + array would be a 3d grayscale image, not an RGB image. This choice + facilitates efficient and type-stable indexing behavior and enhances + consistency. "Lazy" interconversion between arbitrary numeric + arrays and color arrays are provided by two new view types, + `colorview` and `channelview`, defined in the `ImageCore` package. + These types hopefully remove any awkwardness from the new requirement. + +- For an indexed (colormap) image `imgi`, indexing with `imgi[i,j]` + used to return the *index*, not the *pixel value*. This operation now + returns the *pixel value*, with the consequence that indexed images + largely act like the array they represent. Indexed images are defined + and handled by the `IndirectArrays` package. + +- Image filtering has been greatly improved. `imfilter_fft` and + `imfilter_gaussian` have both been rolled into `imfilter`. FFT/FIR + filtering is chosen automatically (though a choice can be specified) + depending on kernel size, aiming for the best performance in all + cases and a consistent interface for specifying defaults. The main + filtering algorithms have been considerably improved, particularly + for separable kernels, and feature cache-efficient tiling and + multithreading. The performance enhancement is as large as 10-fold + in some cases, particularly when starting Julia with multiple + threads. Certain constructs for specifying boundary conditions have + been deprecated and replaced with dispatch-leveraging + alternatives. Specification of standard kernels has been changed + considerably, and has been split out into two modules, `Kernel` and + `KernelFactors`, both defined in the `ImageFiltering` package. + +- Previous versions of Images used `reinterpret` for several + operations, but `reinterpret` fails for most `AbstractArray`s other + than `Array`. This release implements alternative mechanisms (e.g., + based on the `MappedArrays` package) that work for any + `AbstractArray` type. (When it would help performance, `reinterpret` + is still used when applicable.) Consequently, this release features + better support for a wider range of array types. + +# Older versions + +For earlier history, please see the git revision history. diff --git a/src/Images.jl b/src/Images.jl index 02ec3268..e2665554 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -62,6 +62,7 @@ include("edge.jl") include("writemime.jl") include("corner.jl") include("distances.jl") +include("deprecated.jl") export # types AbstractImage, diff --git a/src/deprecated.jl b/src/deprecated.jl new file mode 100644 index 00000000..bd162d47 --- /dev/null +++ b/src/deprecated.jl @@ -0,0 +1,6 @@ +const yx = ["y", "x"] +const xy = ["x", "y"] + +SliceData(args...) = error("SliceData has been removed, please use julia's regular indexing operations") +reslice!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") +rerange!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") diff --git a/test/algorithms.jl b/test/old/algorithms.jl similarity index 100% rename from test/algorithms.jl rename to test/old/algorithms.jl diff --git a/test/core.jl b/test/old/core.jl similarity index 74% rename from test/core.jl rename to test/old/core.jl index b0c2efce..3bbd8e65 100644 --- a/test/core.jl +++ b/test/old/core.jl @@ -1,4 +1,4 @@ -using FactCheck, Images, Colors, FixedPointNumbers +using FactCheck, Images, Colors, FixedPointNumbers, IndirectArrays, AxisArrays using Compat using Compat.view @@ -13,46 +13,48 @@ facts("Core") do a = rand(3,3) @inferred(Image(a)) # support integer-valued types, but these are NOT recommended (use UFixed) - B = rand(convert(UInt16, 1):convert(UInt16, 20), 3, 5) + B = rand(UInt16(1):UInt16(20), 3, 5) # img, imgd, and imgds will be used in many more tests # Thus, these must be defined as local if reassigned in any context() blocks - cmap = reinterpret(RGB, repmat(reinterpret(UFixed8, [round(UInt8, x) for x in linspace(12, 255, 20)])', 3, 1)) - img = ImageCmap(copy(B), cmap, Dict{Compat.ASCIIString, Any}([("pixelspacing", [2.0, 3.0]), ("spatialorder", Images.yx)])) - imgd = convert(Image, img) + cmap = reinterpret(RGB, repmat(reinterpret(UFixed8, round(UInt8, linspace(12, 255, 20)))', 3, 1)) + imgi = IndirectArray(copy(B), cmap) + img = AxisArray(imgi, Axis{:y}(2*(1:size(imgi,1))), Axis{:x}(3*(1:size(imgi,2)))) + imgd0 = convert(Array, imgi) if testing_units - imgd["pixelspacing"] = [2.0mm, 3.0mm] + imgd = AxisArray(imgd0, + Axis{:y}(2mm:2mm:2*size(imgi,1)*mm), + Axis{:x}(3mm:3mm:3*size(imgi,2)*mm)) + else + imgd = AxisArray(imgd0, axes(img)...) end imgds = separate(imgd) context("Constructors of Image types") do @fact colorspace(B) --> "Gray" @fact colordim(B) --> 0 - let img = Image(B, colorspace="RGB", colordim=1) # keyword constructor + let img = colorview(RGB, ufixedview(UFixed{UInt16,8}, B)) @fact colorspace(img) --> "RGB" - @fact colordim(img) --> 1 + @fact colordim(img) --> 0 img = grayim(B) @fact colorspace(img) --> "Gray" @fact colordim(B) --> 0 @fact grayim(img) --> img - # this is recommended for "integer-valued" images (or even better, directly as a UFixed type) - # Work around poor inference and no shape preserving comprehension - # on 0.4..... Change to `round.(UInt8, B)` on 0.6 - Bf = grayim(convert(Array{UInt8}, @compat round.([UInt8], B))) - @fact eltype(Bf) --> UFixed8 + Bf = grayim(round(UInt8, B)) + @fact eltype(Bf) --> Gray{UFixed8} @fact colorspace(Bf) --> "Gray" @fact colordim(Bf) --> 0 Bf = grayim(B) - @fact eltype(Bf) --> UFixed16 + @fact eltype(Bf) --> Gray{UFixed16} # colorspace encoded as a Color (enables multiple dispatch) BfCV = reinterpret(Gray{UFixed8}, [round(UInt8, x) for x in B]) @fact colorspace(BfCV) --> "Gray" @fact colordim(BfCV) --> 0 Bf3 = grayim(reshape(collect(convert(UInt8,1):convert(UInt8,36)), 3,4,3)) - @fact eltype(Bf3) --> UFixed8 + @fact eltype(Bf3) --> Gray{UFixed8} Bf3 = grayim(reshape(collect(convert(UInt16,1):convert(UInt16,36)), 3,4,3)) - @fact eltype(Bf3) --> UFixed16 + @fact eltype(Bf3) --> Gray{UFixed16} Bf3 = grayim(reshape(collect(1.0f0:36.0f0), 3,4,3)) - @fact eltype(Bf3) --> Float32 + @fact eltype(Bf3) --> Gray{Float32} end end @@ -63,9 +65,9 @@ facts("Core") do @fact colorim(C) --> C C = colorim(rand(UInt16, 4, 5, 5), "ARGB") @fact eltype(C) --> ARGB{UFixed16} - C = colorim(rand(1:20, 3, 5, 5)) - @fact eltype(C) --> Int - @fact colordim(C) --> 1 + C = colorim(rand(UInt8(1):UInt8(20), 3, 5, 5)) + @fact eltype(C) --> RGB{U8} + @fact colordim(C) --> 0 @fact colorspace(C) --> "RGB" @fact eltype(colorim(rand(UInt16, 3, 5, 5))) --> RGB{UFixed16} @fact eltype(colorim(rand(3, 5, 5))) --> RGB{Float64} @@ -88,9 +90,9 @@ facts("Core") do context("Indexed color") do let cmap = linspace(RGB(0x0cuf8, 0x00uf8, 0x00uf8), RGB(0xffuf8, 0x00uf8, 0x00uf8), 20) - img_ = ImageCmap(copy(B), cmap, Dict{Compat.ASCIIString, Any}([("spatialorder", Images.yx)])) + img_ = ImageCmap(copy(B), cmap) @fact colorspace(img_) --> "RGB" - img_ = ImageCmap(copy(B), cmap, spatialorder=Images.yx) + img_ = ImageCmap(copy(B), cmap) @fact colorspace(img_) --> "RGB" # Note: img from opening of facts() block # TODO: refactor whole block @@ -130,7 +132,7 @@ facts("Core") do @fact colordim(1:5) --> 0 @fact nimages(1:5) --> 1 @fact colorspace(1:5) --> "Gray" - img1 = Image(map(Gray, 0.1:0.1:0.5), spatialorder=["z"]) + img1 = AxisArray(map(Gray, 0.1:0.1:0.5), :z) @fact colordim(img1) --> 0 @fact colorspace(img1) --> "Gray" @fact sdims(img1) --> 1 @@ -167,17 +169,6 @@ facts("Core") do @fact shareproperties(img, B) --> B end - context("Getindex / setindex!") do - prev = img[4] - @fact prev --> B[4] - img[4] = prev+1 - @fact img.data[4] --> prev+1 - @fact img[4] --> prev+1 - @fact img[1,2] --> prev+1 - img[1,2] = prev - @fact img[4] --> prev - end - context("Properties") do @fact colorspace(img) --> "RGB" @fact colordim(img) --> 0 @@ -197,20 +188,13 @@ facts("Core") do @fact coords_spatial(img) --> coords_spatial(imgd) @fact size_spatial(img) --> size_spatial(imgd) A = randn(3,5,3) - tmp = Image(A, Dict{Compat.ASCIIString,Any}()) - copy!(tmp, imgd, "spatialorder") - @fact properties(tmp) --> Dict{Compat.ASCIIString,Any}([("spatialorder",Images.yx)]) - copy!(tmp, imgd, "spatialorder", "pixelspacing") - if testing_units - @fact tmp["pixelspacing"] --> [2.0mm, 3.0mm] - end @fact storageorder(img) --> Images.yx @fact storageorder(imgds) --> [Images.yx; "color"] A = rand(4,4,3) @fact colordim(A) --> 3 - Aimg = permutedims(convert(Image, A), [3,1,2]) + Aimg = permutedims(convert(ImageMeta, A), [3,1,2]) @fact colordim(Aimg) --> 1 end @@ -285,13 +269,14 @@ facts("Core") do @fact colordim(s) --> 3 @fact colorspace(s) --> "Unknown" @fact spatialorder(s) --> ["y","x"] - s = view(img, "y", 2:2) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,5) - s = view(img, "y", 2) - @fact ndims(s) --> 1 - @fact size(s) --> (5,) + # FIXME uncomment these +# s = view(img, "y", 2:2) + # @fact ndims(s) --> 2 + # @fact sdims(s) --> 2 + # @fact size(s) --> (1,5) + # s = view(img, "y", 2) + # @fact ndims(s) --> 1 + # @fact size(s) --> (5,) @fact size(getindexim(imgds, :, 1:2, :)) --> (size(imgds,1), 2, 3) s = permutedims(imgds, (3,1,2)) @@ -310,32 +295,6 @@ facts("Core") do @fact spatialorder(sss) --> ["x"] end -# # reslicing -# D = randn(3,5,4) -# sd = SliceData(D, 2) -# C = slice(D, sd, 2) -# @fact C --> reshape(D[1:end, 2, 1:end], size(C)) -# reslice!(C, sd, 3) -# @fact C --> reshape(D[1:end, 3, 1:end], size(C)) -# sd = SliceData(D, 3) -# C = slice(D, sd, 2) -# @fact C --> reshape(D[1:end, 1:end, 2], size(C)) -# -# sd = SliceData(imgds, 2) -# s = sliceim(imgds, sd, 2) -# @fact colordim(s) --> 2 -# @fact colorspace(s) --> "RGB" -# @fact spatialorder(s) --> ["y"] -# @fact s.data --> reshape(imgds[:,2,:], size(s)) -# sd = SliceData(imgds, 3) -# s = sliceim(imgds, sd, 2) -# @fact colordim(s) --> 0 -# @fact colorspace(s) --> "Unknown" -# @fact spatialorder(s) --> Images.yx -# @fact s.data --> imgds[:,:,2] -# reslice!(s, sd, 3) -# @fact s.data --> imgds[:,:,3] - context("Named indexing") do @fact dimindex(imgds, "color") --> 3 @fact dimindex(imgds, "y") --> 1 @@ -349,41 +308,10 @@ facts("Core") do chan = imgds["color", 2] Blookup = reshape(green(cmap[B[:]]), size(B)) @fact chan --> Blookup - - sd = SliceData(imgds, "x") - s = sliceim(imgds, sd, 2) - @fact spatialorder(s) --> ["y"] - @fact s.data --> reshape(imgds[:,2,:], size(s)) - sd = SliceData(imgds, "y") - s = sliceim(imgds, sd, 2) - @fact spatialorder(s) --> ["x"] - @fact s.data --> reshape(imgds[2,:,:], size(s)) - sd = SliceData(imgds, "x", "y") - s = sliceim(imgds, sd, 2, 1) - @fact s.data --> reshape(imgds[1,2,:], 3) end context("Spatial order, width/ height, and permutations") do - @fact spatialpermutation(Images.yx, imgds) --> [1,2] @fact widthheight(imgds) --> (5,3) - C = convert(Array, imgds) - @fact C --> imgds.data - imgds["spatialorder"] = ["x", "y"] - @fact spatialpermutation(Images.xy, imgds) --> [1,2] - @fact widthheight(imgds) --> (3,5) - C = convert(Array, imgds) - @fact C --> permutedims(imgds.data, [2,1,3]) - imgds.properties["spatialorder"] = ["y", "x"] - @fact spatialpermutation(Images.xy, imgds) --> [2,1] - imgds.properties["spatialorder"] = ["x", "L"] - @fact spatialpermutation(Images.xy, imgds) --> [1,2] - imgds.properties["spatialorder"] = ["L", "x"] - @fact spatialpermutation(Images.xy, imgds) --> [2,1] - A = randn(3,5,3) - @fact spatialpermutation(Images.xy, A) --> [2,1] - @fact spatialpermutation(Images.yx, A) --> [1,2] - - imgds.properties["spatialorder"] = Images.yx imgp = permutedims(imgds, ["x", "y", "color"]) @fact imgp.data --> permutedims(imgds.data, [2,1,3]) imgp = permutedims(imgds, ("color", "x", "y")) @@ -391,9 +319,8 @@ facts("Core") do if testing_units @fact pixelspacing(imgp) --> [3.0mm, 2.0mm] end - imgc = copy(imgds) + imgc = ImageMeta(copy(imgds)) imgc["spacedirections"] = spacedirections(imgc) - delete!(imgc, "pixelspacing") imgp = permutedims(imgc, ["x", "y", "color"]) if testing_units @fact spacedirections(imgp) --> Vector{SIUnits.SIQuantity{Float64,1,0,0,0,0,0,0}}[[0.0mm, 3.0mm],[2.0mm, 0.0mm]] @@ -440,9 +367,6 @@ facts("Core") do @fact convert(Image, ims8) --> exactly(ims8) @fact convert(Image{UFixed8}, ims8) --> exactly(ims8) @fact separate(ims8) --> exactly(ims8) - imrgb8_2 = convert(Image{RGB}, ims8) - @fact isa(imrgb8_2, Image{RGB{UFixed8}}) --> true - @fact imrgb8_2 --> imrgb8 A = reinterpret(UFixed8, UInt8[1 2; 3 4]) imgray = convert(Image{Gray{UFixed8}}, A) @fact spatialorder(imgray) --> Images.yx @@ -468,11 +392,5 @@ facts("Core") do @fact typeof(raw(imgdata)) --> typeof(imgdata) # check array fallback @fact all(raw(imgdata) .== imgdata) --> true end - # Issue #497 - let img = colorim(rand(3, 5, 5)) - img["colorspace"] = "sRGB" - imgg = convert(Image{Gray}, img) - @fact haskey(imgg, "colorspace") --> false - end end end diff --git a/test/corner.jl b/test/old/corner.jl similarity index 100% rename from test/corner.jl rename to test/old/corner.jl diff --git a/test/distances.jl b/test/old/distances.jl similarity index 100% rename from test/distances.jl rename to test/old/distances.jl diff --git a/test/edge.jl b/test/old/edge.jl similarity index 100% rename from test/edge.jl rename to test/old/edge.jl diff --git a/test/exposure.jl b/test/old/exposure.jl similarity index 100% rename from test/exposure.jl rename to test/old/exposure.jl diff --git a/test/map.jl b/test/old/map.jl similarity index 100% rename from test/map.jl rename to test/old/map.jl diff --git a/test/overlays.jl b/test/old/overlays.jl similarity index 100% rename from test/overlays.jl rename to test/old/overlays.jl diff --git a/test/parallel.jl b/test/old/parallel.jl similarity index 100% rename from test/parallel.jl rename to test/old/parallel.jl diff --git a/test/restrict.jl b/test/old/restrict.jl similarity index 100% rename from test/restrict.jl rename to test/old/restrict.jl diff --git a/test/old/runtests.jl b/test/old/runtests.jl new file mode 100644 index 00000000..a0cf91bc --- /dev/null +++ b/test/old/runtests.jl @@ -0,0 +1,26 @@ +module ImagesTests + +using FactCheck, Base.Test, Images, Colors, FixedPointNumbers +using Graphics + +testing_units = Int == Int64 +if testing_units + using SIUnits, SIUnits.ShortUnits +end + + +include("core.jl") +include("map.jl") +include("overlays.jl") +include("algorithms.jl") +include("exposure.jl") +include("edge.jl") +include("writemime.jl") +include("corner.jl") +include("distances.jl") + +isinteractive() || FactCheck.exitstatus() + +end + +include("parallel.jl") diff --git a/test/writemime.jl b/test/old/writemime.jl similarity index 100% rename from test/writemime.jl rename to test/old/writemime.jl diff --git a/test/runtests.jl b/test/runtests.jl index 8b8148fe..09dd9926 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,27 +1 @@ -module ImagesTests - -using FactCheck, Base.Test, Images, Colors, FixedPointNumbers -using Graphics -using Compat - -testing_units = Int == Int64 -if testing_units - using SIUnits, SIUnits.ShortUnits -end - - -include("core.jl") -include("map.jl") -include("overlays.jl") -include("algorithms.jl") -include("exposure.jl") -include("edge.jl") -include("writemime.jl") -include("corner.jl") -include("distances.jl") - -isinteractive() || FactCheck.exitstatus() - -end - -include("parallel.jl") +include("old/runtests.jl") From da7384ca30b2f194076c14a17ac3e9cd3ceec844 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 18 Sep 2016 11:22:51 -0500 Subject: [PATCH 03/38] Get the core tests passing and add a label to each test Mostly this relied on changes in other packages. The labels are useful because FactCheck messes up line numbers in reporting. --- src/Images.jl | 1 - src/core.jl | 39 +--- test/old/core.jl | 449 +++++++++++++++++++++++------------------------ 3 files changed, 222 insertions(+), 267 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index e2665554..822e484a 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -108,7 +108,6 @@ export # types coords_spatial, copyproperties, data, - dimindex, dimindexes, getindexim, grayim, diff --git a/src/core.jl b/src/core.jl index 16ff4ba3..f0ec88c4 100644 --- a/src/core.jl +++ b/src/core.jl @@ -1432,10 +1432,9 @@ showdictelem(io::IO, v) = showcompact(io, v) # where anything not mentioned by name is assumed to include the whole range function coords(img::AbstractImage, dimname::String, ind, nameind...) c = Any[1:d for d in size(img)] - so = spatialorder(img) - c[require_dimindex(img, dimname, so)] = ind + c[require_dimindex(img, dimname)] = ind for i = 1:2:length(nameind) - c[require_dimindex(img, nameind[i], so)] = nameind[i+1] + c[require_dimindex(img, nameind[i])] = nameind[i+1] end tuple(c...) end @@ -1446,42 +1445,14 @@ function coords(img::AbstractImage; kwargs...) c = Any[1:d for d in size(img)] so = spatialorder(img) for (k, v) in kwargs - c[require_dimindex(img, string(k), so)] = v + c[require_dimindex(img, string(k))] = v end tuple(c...) end +require_dimindex(img::AbstractImage, dimname) = (di = dimindex(img, dimname); di > 0 || error("No dimension called ", dimname); di) -function dimindex(img::AbstractImage, dimname::String, so = spatialorder(img)) - n::Int = 0 - if dimname == "color" - n = colordim(img) - elseif dimname == "t" - n = timedim(img) - else - cd = colordim(img) - td = timedim(img) - j = 1 - tn = 1 - while j <= length(so) - while tn == cd || tn == td - tn += 1 - end - if dimname == so[j] - n = tn - break - end - tn += 1 - j += 1 - end - end - n -end - - -require_dimindex(img::AbstractImage, dimname, so) = (di = dimindex(img, dimname, so); di > 0 || error("No dimension called ", dimname); di) - -dimindexes(img::AbstractImage, dimnames::AbstractString...) = Int[dimindex(img, nam, spatialorder(img)) for nam in dimnames] +dimindexes(img::AbstractImage, dimnames::AbstractString...) = Int[dimindex(img, nam) for nam in dimnames] to_vector(v::AbstractVector) = v to_vector(v::Tuple) = [v...] diff --git a/test/old/core.jl b/test/old/core.jl index 3bbd8e65..cde9076c 100644 --- a/test/old/core.jl +++ b/test/old/core.jl @@ -30,115 +30,111 @@ facts("Core") do imgds = separate(imgd) context("Constructors of Image types") do - @fact colorspace(B) --> "Gray" - @fact colordim(B) --> 0 + @fact colorspace(B) --> "Gray" "test h42DSP" + @fact colordim(B) --> 0 "test 4ZQwBT" let img = colorview(RGB, ufixedview(UFixed{UInt16,8}, B)) - @fact colorspace(img) --> "RGB" - @fact colordim(img) --> 0 + @fact colorspace(img) --> "RGB" "test THoeTd" + @fact colordim(img) --> 0 "test Zm2gDZ" img = grayim(B) - @fact colorspace(img) --> "Gray" - @fact colordim(B) --> 0 - @fact grayim(img) --> img + @fact colorspace(img) --> "Gray" "test SIFbZo" + @fact colordim(B) --> 0 "test HAdcG0" + @fact grayim(img) --> img "test mJppbD" Bf = grayim(round(UInt8, B)) - @fact eltype(Bf) --> Gray{UFixed8} - @fact colorspace(Bf) --> "Gray" - @fact colordim(Bf) --> 0 + @fact eltype(Bf) --> Gray{UFixed8} "test luvkCN" + @fact colorspace(Bf) --> "Gray" "test JwE99B" + @fact colordim(Bf) --> 0 "test DNzikz" Bf = grayim(B) - @fact eltype(Bf) --> Gray{UFixed16} + @fact eltype(Bf) --> Gray{UFixed16} "test CPd4Fa" # colorspace encoded as a Color (enables multiple dispatch) - BfCV = reinterpret(Gray{UFixed8}, [round(UInt8, x) for x in B]) - @fact colorspace(BfCV) --> "Gray" - @fact colordim(BfCV) --> 0 + BfCV = reinterpret(Gray{UFixed8}, round(UInt8, B)) + @fact colorspace(BfCV) --> "Gray" "test Tp3xdg" + @fact colordim(BfCV) --> 0 "test aALUCW" Bf3 = grayim(reshape(collect(convert(UInt8,1):convert(UInt8,36)), 3,4,3)) - @fact eltype(Bf3) --> Gray{UFixed8} + @fact eltype(Bf3) --> Gray{UFixed8} "test k8hBgR" Bf3 = grayim(reshape(collect(convert(UInt16,1):convert(UInt16,36)), 3,4,3)) - @fact eltype(Bf3) --> Gray{UFixed16} + @fact eltype(Bf3) --> Gray{UFixed16} "test KASRod" Bf3 = grayim(reshape(collect(1.0f0:36.0f0), 3,4,3)) - @fact eltype(Bf3) --> Gray{Float32} + @fact eltype(Bf3) --> Gray{Float32} "test Vr9iDW" end end context("Colorim") do C = colorim(rand(UInt8, 3, 5, 5)) - @fact eltype(C) --> RGB{UFixed8} - @fact colordim(C) --> 0 - @fact colorim(C) --> C + @fact eltype(C) --> RGB{UFixed8} "test N0iGXo" + @fact colordim(C) --> 0 "test 6COaeI" + @fact colorim(C) --> C "test hPgSHp" C = colorim(rand(UInt16, 4, 5, 5), "ARGB") - @fact eltype(C) --> ARGB{UFixed16} + @fact eltype(C) --> ARGB{UFixed16} "test PZ5Ld5" C = colorim(rand(UInt8(1):UInt8(20), 3, 5, 5)) - @fact eltype(C) --> RGB{U8} - @fact colordim(C) --> 0 - @fact colorspace(C) --> "RGB" - @fact eltype(colorim(rand(UInt16, 3, 5, 5))) --> RGB{UFixed16} - @fact eltype(colorim(rand(3, 5, 5))) --> RGB{Float64} - @fact colordim(colorim(rand(UInt8, 5, 5, 3))) --> 3 - @fact spatialorder(colorim(rand(UInt8, 3, 5, 5))) --> ["x", "y"] - @fact spatialorder(colorim(rand(UInt8, 5, 5, 3))) --> ["y", "x"] - @fact eltype(colorim(rand(UInt8, 4, 5, 5), "RGBA")) --> RGBA{UFixed8} - @fact eltype(colorim(rand(UInt8, 4, 5, 5), "ARGB")) --> ARGB{UFixed8} - @fact colordim(colorim(rand(UInt8, 5, 5, 4), "RGBA")) --> 3 - @fact colordim(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> 3 - @fact spatialorder(colorim(rand(UInt8, 4, 5, 5), "ARGB")) --> ["x", "y"] - @fact spatialorder(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> ["y", "x"] - @fact_throws ErrorException colorim(rand(UInt8, 3, 5, 3)) - @fact_throws ErrorException colorim(rand(UInt8, 4, 5, 5)) - @fact_throws ErrorException colorim(rand(UInt8, 5, 5, 4)) - @fact_throws ErrorException colorim(rand(UInt8, 4, 5, 4), "ARGB") - @fact_throws ErrorException colorim(rand(UInt8, 5, 5, 5), "foo") - @fact_throws ErrorException colorim(rand(UInt8, 2, 2, 2), "bar") + @fact eltype(C) --> RGB{U8} "test MyDpnz" + @fact colordim(C) --> 0 "test vu1pXs" + @fact colorspace(C) --> "RGB" "test moBy0x" + @fact eltype(colorim(rand(UInt16, 3, 5, 5))) --> RGB{UFixed16} "test pzSy1a" + @fact eltype(colorim(rand(3, 5, 5))) --> RGB{Float64} "test F3ex39" + @fact colordim(colorim(rand(UInt8, 5, 5, 3))) --> 0 "test kElkKH" + @fact spatialorder(colorim(rand(UInt8, 3, 5, 5))) --> (:y, :x) "test auwSni" + @fact spatialorder(colorim(rand(UInt8, 5, 5, 3))) --> (:y, :x) "test g307S1" + @fact eltype(colorim(rand(UInt8, 4, 5, 5), "RGBA")) --> RGBA{UFixed8} "test RcSRnh" + @fact eltype(colorim(rand(UInt8, 4, 5, 5), "ARGB")) --> ARGB{UFixed8} "test 9hscsz" + @fact colordim(colorim(rand(UInt8, 5, 5, 4), "RGBA")) --> 0 "test SJTuxu" + @fact colordim(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> 0 "test lIpIVN" + @fact spatialorder(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> (:y, :x) "test y0Vpv4" + @fact_throws ErrorException colorim(rand(UInt8, 3, 5, 3)) "test zCDA2C" + @fact_throws ErrorException colorim(rand(UInt8, 4, 5, 5)) "test evgCjW" + @fact_throws ErrorException colorim(rand(UInt8, 5, 5, 4)) "test ByYXn6" + @fact_throws ErrorException colorim(rand(UInt8, 4, 5, 4), "ARGB") "test E8cclV" + @fact_throws ErrorException colorim(rand(UInt8, 5, 5, 5), "foo") "test JHb2sd" + @fact_throws ErrorException colorim(rand(UInt8, 2, 2, 2), "bar") "test eVybiQ" end context("Indexed color") do let cmap = linspace(RGB(0x0cuf8, 0x00uf8, 0x00uf8), RGB(0xffuf8, 0x00uf8, 0x00uf8), 20) img_ = ImageCmap(copy(B), cmap) - @fact colorspace(img_) --> "RGB" + @fact colorspace(img_) --> "RGB" "test ekvCpM" img_ = ImageCmap(copy(B), cmap) - @fact colorspace(img_) --> "RGB" + @fact colorspace(img_) --> "RGB" "test F3LOhS" # Note: img from opening of facts() block # TODO: refactor whole block - @fact eltype(img) --> RGB{UFixed8} - @fact eltype(imgd) --> RGB{UFixed8} + @fact eltype(img) --> RGB{UFixed8} "test 8aSK8W" + @fact eltype(imgd) --> RGB{UFixed8} "test IhyuSH" end end context("Basic information") do - @fact size(img) --> (3,5) - @fact size(imgd) --> (3,5) - @fact ndims(img) --> 2 - @fact ndims(imgd) --> 2 - @fact size(img,"y") --> 3 - @fact size(img,"x") --> 5 - @fact strides(img) --> (1,3) - @fact strides(imgd) --> (1,3) - @fact strides(imgds) --> (1,3,15) - @fact nimages(img) --> 1 - @fact ncolorelem(img) --> 1 - @fact ncolorelem(imgd) --> 1 - @fact ncolorelem(imgds) --> 3 + @fact size(img) --> (3,5) "test SVoECb" + @fact size(imgd) --> (3,5) "test GEziWW" + @fact ndims(img) --> 2 "test uD5ST6" + @fact ndims(imgd) --> 2 "test Gfuga9" + @fact size(img,"y") --> 3 "test 6tVTzS" + @fact size(img,"x") --> 5 "test WYg5qs" + @fact strides(img) --> (1,3) "test lH4UyT" + @fact strides(imgd) --> (1,3) "test Rvvapy" + @fact strides(imgds) --> (1,3,15) "test dYffuB" + @fact nimages(img) --> 1 "test jE9DjL" + @fact ncolorelem(img) --> 1 "test W1Jzs4" + @fact ncolorelem(imgd) --> 1 "test 76okNe" + @fact ncolorelem(imgds) --> 1 "test xtbHDi" vimg = vec(img) - @fact isa(vimg, ImageCmap) --> true - @fact haskey(vimg, "spatialorder") --> false - @fact haskey(vimg, "pixelspacing") --> false - @fact sort(vimg)[1] --> minimum(img) + @fact haskey(vimg, "spatialorder") --> false "test Jvo4UH" + @fact haskey(vimg, "pixelspacing") --> false "test D37jeN" vimgds = vec(imgds) - @fact isa(vimgds, Image) --> true - @fact haskey(vimg, "colordim") --> false - @fact haskey(vimg, "colorspace") --> false - @fact sort(vimgds)[1] --> minimum(imgds) + @fact haskey(vimg, "colordim") --> false "test nYSqMQ" + @fact haskey(vimg, "colorspace") --> false "test OLYptD" + @fact sort(vimgds)[1] --> minimum(imgds) "test gTnGfY" end context("1-dimensional images") do - @fact colordim(1:5) --> 0 - @fact nimages(1:5) --> 1 - @fact colorspace(1:5) --> "Gray" + @fact colordim(1:5) --> 0 "test ZzuntI" + @fact nimages(1:5) --> 1 "test qATU2x" + @fact colorspace(1:5) --> "Gray" "test urhFyz" img1 = AxisArray(map(Gray, 0.1:0.1:0.5), :z) - @fact colordim(img1) --> 0 - @fact colorspace(img1) --> "Gray" - @fact sdims(img1) --> 1 - @fact convert(Vector{Gray{Float64}}, img1) --> map(Gray, 0.1:0.1:0.5) - @fact size(img1, "z") --> 5 - @fact_throws ErrorException size(img1, "x") + @fact colordim(img1) --> 0 "test w9LJFP" + @fact colorspace(img1) --> "Gray" "test bTdx4f" + @fact sdims(img1) --> 1 "test fImXGd" + @fact convert(Vector{Gray{Float64}}, img1) --> map(Gray, 0.1:0.1:0.5) "test sx0MrI" + @fact size(img1, "z") --> 5 "test UVd1Qc" + @fact_throws ErrorException size(img1, "x") "test w99nLd" end context("Printing") do @@ -150,247 +146,236 @@ facts("Core") do context("Copy / similar") do A = randn(3,5,3) imgc = @inferred(copy(img)) - @fact imgc.data --> img.data + @fact imgc.data --> img.data "test bmmvpl" imgc = copyproperties(imgd, A) - @fact imgc.data --> A + @fact imgc --> A "test FtSoza" img2 = @inferred(similar(img)) - @fact isa(img2, ImageCmap) --> true - @fact (img2.data == img.data) --> false + @fact isa(img2, AxisArray) --> true "test ZQlGw2" + @fact (img2.data == img.data) --> false "test Fis1BF" img2 = @inferred(similar(imgd)) - @fact isa(img2, Image) --> true + @fact isa(img2, typeof(imgd)) --> true "test kWPhBO" img2 = similar(img, (4,4)) - @fact isa(img2, ImageCmap) --> true - @fact size(img2) --> (4,4) + @fact size(img2) --> (4,4) "test XM2fWe" img2 = similar(imgd, (3,4,4)) - @fact isa(img2, Image) --> true - @fact size(img2) --> (3,4,4) - @fact copyproperties(B, A) --> A - @fact shareproperties(A, B) --> B - @fact shareproperties(img, B) --> B + @fact size(img2) --> (3,4,4) "test CDWvHz" + @fact copyproperties(B, A) --> A "test JQUH5M" + @fact shareproperties(A, B) --> B "test xfed58" + @fact shareproperties(img, B) --> B "test gvgFZu" end context("Properties") do - @fact colorspace(img) --> "RGB" - @fact colordim(img) --> 0 - @fact colordim(imgds) --> 3 - @fact timedim(img) --> 0 - @fact pixelspacing(img) --> [2.0, 3.0] - @fact spacedirections(img) --> Vector{Float64}[[2.0, 0], [0, 3.0]] + @fact colorspace(img) --> "RGB" "test r4GQh5" + @fact colordim(img) --> 0 "test P9R46B" + @fact colordim(imgds) --> 3 "test 7Cmhyv" + @fact timedim(img) --> 0 "test UxEzff" + @fact pixelspacing(img) --> (2, 3) "test xxx4JY" + @fact spacedirections(img) --> ((2, 0), (0, 3)) "test BMHiEE" if testing_units - @fact pixelspacing(imgd) --> [2.0mm, 3.0mm] - @fact spacedirections(imgd) --> - Vector{SIUnits.SIQuantity{Float64,1,0,0,0,0,0,0}}[[2.0mm, 0.0mm], [0.0mm, 3.0mm]] + @fact pixelspacing(imgd) --> (2mm, 3mm) "test Io4D8M" + @fact spacedirections(imgd) --> ((2mm, 0mm), (0mm, 3mm)) "test 1yJxFQ" end end context("Dims and ordering") do - @fact sdims(img) --> sdims(imgd) - @fact coords_spatial(img) --> coords_spatial(imgd) - @fact size_spatial(img) --> size_spatial(imgd) + @fact sdims(img) --> sdims(imgd) "test qbjpWy" + @fact coords_spatial(img) --> coords_spatial(imgd) "test UBaUrc" + @fact size_spatial(img) --> size_spatial(imgd) "test Z6fpfp" A = randn(3,5,3) - @fact storageorder(img) --> Images.yx - @fact storageorder(imgds) --> [Images.yx; "color"] - - A = rand(4,4,3) - @fact colordim(A) --> 3 - Aimg = permutedims(convert(ImageMeta, A), [3,1,2]) - @fact colordim(Aimg) --> 1 + @fact storageorder(img) --> (Symbol.(Images.yx)...,) "test FbZZeV" + @fact storageorder(imgds) --> (Symbol.(Images.yx)..., :color) "test PthQLh" end context("Sub / slice") do s = view(img, 2:2, 1:4) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,4) - @fact data(s) --> view(B, 2:2, 1:4) + @fact ndims(s) --> 2 "test zKaJui" + @fact sdims(s) --> 2 "test UZP9Ss" + @fact size(s) --> (1,4) "test Fj3Elk" + @fact data(s) --> cmap[view(B, 2:2, 1:4)] "test MEE0Kf" if scalar_getindex_new s = getindexim(img, 2, 1:4) - @fact ndims(s) --> 1 - @fact sdims(s) --> 1 - @fact size(s) --> (4,) - @fact data(s) --> B[2, 1:4] + @fact ndims(s) --> 1 "test oUbfko" + @fact sdims(s) --> 1 "test gGEDEE" + @fact size(s) --> (4,) "test iiUIN1" + @fact data(s) --> cmap[B[2, 1:4]] "test J3Eh9N" s = getindexim(img, 2:2, 1:4) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,4) - @fact data(s) --> B[2:2, 1:4] + @fact ndims(s) --> 2 "test TqlvGx" + @fact sdims(s) --> 2 "test m1UOXa" + @fact size(s) --> (1,4) "test Y28Rxj" + @fact data(s) --> cmap[B[2:2, 1:4]] "test w2w6dq" else s = getindexim(img, 2, 1:4) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,4) - @fact data(s) --> B[2, 1:4] + @fact ndims(s) --> 2 "test HucgqQ" + @fact sdims(s) --> 2 "test OPRJZ2" + @fact size(s) --> (1,4) "test rKuBuM" + @fact data(s) --> B[2, 1:4] "test CBcZSh" end s = subim(img, 2, 1:4) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,4) + @fact ndims(s) --> 2 "test u6Ga7f" + @fact sdims(s) --> 2 "test RDsYNq" + @fact size(s) --> (1,4) "test L390gv" s = subim(img, 2, [1,2,4]) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact size(s) --> (1,3) + @fact ndims(s) --> 2 "test qvMWQI" + @fact sdims(s) --> 2 "test sEei68" + @fact size(s) --> (1,3) "test pRQ45S" s = sliceim(img, 2, 1:4) - @fact ndims(s) --> 1 - @fact sdims(s) --> 1 - @fact size(s) --> (4,) + @fact ndims(s) --> 1 "test fAmwu6" + @fact sdims(s) --> 1 "test hfxc7J" + @fact size(s) --> (4,) "test 6xHsFS" s = sliceim(img, 2, [1,2,4]) - @fact ndims(s) --> 1 - @fact sdims(s) --> 1 - @fact size(s) --> (3,) + @fact ndims(s) --> 1 "test td0QjR" + @fact sdims(s) --> 1 "test akcJpS" + @fact size(s) --> (3,) "test AUktx0" s = sliceim(imgds, 2, 1:4, 1:3) - @fact ndims(s) --> 2 - @fact sdims(s) --> 1 - @fact colordim(s) --> 2 - @fact spatialorder(s) --> ["x"] + @fact ndims(s) --> 2 "test 2tJFtL" + @fact sdims(s) --> 1 "test SYKGkr" + @fact colordim(s) --> 2 "test lPN4QA" + @fact spatialorder(s) --> (:x,) "test 2OoCHQ" s = sliceim(imgds, 2, :, 1:3) - @fact ndims(s) --> 2 - @fact sdims(s) --> 1 - @fact colordim(s) --> 2 - @fact spatialorder(s) --> ["x"] + @fact ndims(s) --> 2 "test rfr9cD" + @fact sdims(s) --> 1 "test IH9aUL" + @fact colordim(s) --> 2 "test wamZwZ" + @fact spatialorder(s) --> (:x,) "test 6gJq01" s = sliceim(imgds, 2:2, 1:4, 1:3) - @fact ndims(s) --> 3 - @fact sdims(s) --> 2 - @fact colordim(s) --> 3 - @fact colorspace(s) --> "RGB" + @fact ndims(s) --> 3 "test Kxj25e" + @fact sdims(s) --> 2 "test L1qDs3" + @fact colordim(s) --> 3 "test BGZevb" + @fact colorspace(s) --> "Gray" "test U8NVOG" s = getindexim(imgds, 2:2, 1:4, 2) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact colordim(s) --> 0 - @fact colorspace(s) --> "Unknown" + @fact ndims(s) --> 2 "test nKN91R" + @fact sdims(s) --> 2 "test SFKKhD" + @fact colordim(s) --> 0 "test pu5AHT" + @fact colorspace(s) --> "Gray" "test YUBU3N" s = sliceim(imgds, 2:2, 1:4, 2) - @fact ndims(s) --> 2 - @fact sdims(s) --> 2 - @fact colordim(s) --> 0 - @fact colorspace(s) --> "Unknown" + @fact ndims(s) --> 2 "test 9jeHx1" + @fact sdims(s) --> 2 "test uDCJHl" + @fact colordim(s) --> 0 "test 30wLdG" + @fact colorspace(s) --> "Gray" "test KTknYZ" s = sliceim(imgds, 2:2, 1:4, 1:2) - @fact ndims(s) --> 3 + @fact ndims(s) --> 3 "test LxgrvE" + @fact sdims(s) --> 2 "test Wbsn5j" + @fact colordim(s) --> 3 "test 0R4CXY" + @fact colorspace(s) --> "Gray" "test 3o1uhP" + @fact spatialorder(s) --> (:y,:x) "test 9NoVQt" + s = view(img, "y", 2:2) + @fact ndims(s) --> 2 @fact sdims(s) --> 2 - @fact colordim(s) --> 3 - @fact colorspace(s) --> "Unknown" - @fact spatialorder(s) --> ["y","x"] - # FIXME uncomment these -# s = view(img, "y", 2:2) - # @fact ndims(s) --> 2 - # @fact sdims(s) --> 2 - # @fact size(s) --> (1,5) - # s = view(img, "y", 2) - # @fact ndims(s) --> 1 - # @fact size(s) --> (5,) - @fact size(getindexim(imgds, :, 1:2, :)) --> (size(imgds,1), 2, 3) + @fact size(s) --> (1,5) + s = view(img, "y", 2) + @fact ndims(s) --> 1 + @fact size(s) --> (5,) + @fact size(getindexim(imgds, :, 1:2, :)) --> (size(imgds,1), 2, 3) "test Rfvge1" s = permutedims(imgds, (3,1,2)) - @fact colordim(s) --> 1 + @fact colordim(s) --> 1 "test 8Jv9n7" ss = getindexim(s, 2:2, :, :) - @fact colorspace(ss) --> "Unknown" - @fact colordim(ss) --> 1 + @fact colorspace(ss) --> "Gray" "test BEPoQi" + @fact colordim(ss) --> 1 "test hlloiv" sss = squeeze(ss, 1) - @fact colorspace(ss) --> "Unknown" - @fact colordim(sss) --> 0 + @fact colorspace(ss) --> "Gray" "test 6NNgi9" + @fact colordim(sss) --> 0 "test pwQRnh" ss = getindexim(imgds, 2:2, :, :) - @fact colordim(ss) --> 3 - @fact spatialorder(ss) --> ["y", "x"] + @fact colordim(ss) --> 3 "test jx31ut" + @fact spatialorder(ss) --> (:y, :x) "test u11jIc" sss = squeeze(ss, 1) - @fact colordim(sss) --> 2 - @fact spatialorder(sss) --> ["x"] + @fact colordim(sss) --> 2 "test 4X0Hjv" + @fact spatialorder(sss) --> (:x,) "test d5hRJs" end context("Named indexing") do - @fact dimindex(imgds, "color") --> 3 - @fact dimindex(imgds, "y") --> 1 - @fact dimindex(imgds, "z") --> 0 + @fact dimindex(imgds, "color") --> 3 "test EINbuA" + @fact dimindex(imgds, "y") --> 1 "test F4E3tQ" + @fact dimindex(imgds, "z") --> 0 "test 6u1u4T" imgdp = permutedims(imgds, [3,1,2]) - @fact dimindex(imgdp, "y") --> 2 - @fact coords(imgds, "x", 2:4) --> (1:3, 2:4, 1:3) - @fact coords(imgds, x=2:4, y=2:3) --> (2:3, 2:4, 1:3) - @fact img["y", 2, "x", 4] --> B[2,4] - @fact img["x", 4, "y", 2] --> B[2,4] + @fact dimindex(imgdp, "y") --> 2 "test U3pebL" + @fact img["y", 2, "x", 4] --> imgi[2,4] "test oJ519u" + @fact img["x", 4, "y", 2] --> imgi[2,4] "test oCsNbf" chan = imgds["color", 2] Blookup = reshape(green(cmap[B[:]]), size(B)) - @fact chan --> Blookup + @fact chan --> Blookup "test 2cKtWB" end context("Spatial order, width/ height, and permutations") do - @fact widthheight(imgds) --> (5,3) + @fact widthheight(imgds) --> (3,5) "test tgY2FG" imgp = permutedims(imgds, ["x", "y", "color"]) - @fact imgp.data --> permutedims(imgds.data, [2,1,3]) + @fact imgp.data --> permutedims(imgds.data, [2,1,3]) "test MobXxa" imgp = permutedims(imgds, ("color", "x", "y")) - @fact imgp.data --> permutedims(imgds.data, [3,2,1]) + @fact imgp.data --> permutedims(imgds.data, [3,2,1]) "test 1XMuxB" if testing_units - @fact pixelspacing(imgp) --> [3.0mm, 2.0mm] + @fact pixelspacing(imgp) --> (3mm, 2mm) "test 2XyBNB" end imgc = ImageMeta(copy(imgds)) imgc["spacedirections"] = spacedirections(imgc) imgp = permutedims(imgc, ["x", "y", "color"]) if testing_units - @fact spacedirections(imgp) --> Vector{SIUnits.SIQuantity{Float64,1,0,0,0,0,0,0}}[[0.0mm, 3.0mm],[2.0mm, 0.0mm]] - @fact pixelspacing(imgp) --> [3.0mm, 2.0mm] + @fact spacedirections(imgp) --> ((0mm, 3mm),(2mm, 0mm)) "test NUTHJC" + @fact pixelspacing(imgp) --> (3mm, 2mm) "test 5RGPJ0" end end context("Reinterpret, separate, more convert") do a = RGB{Float64}[RGB(1,1,0)] af = reinterpret(Float64, a) - @fact vec(af) --> [1.0,1.0,0.0] - @fact size(af) --> (3,1) - @fact_throws ErrorException reinterpret(Float32, a) + @fact vec(af) --> [1.0,1.0,0.0] "test M3uyLv" + @fact size(af) --> (3,1) "test pLunxL" + @fact_throws DimensionMismatch reinterpret(Float32, a) "test 2GvyKZ" anew = reinterpret(RGB, af) - @fact anew --> a + @fact anew --> a "test ekkFD4" anew = reinterpret(RGB, vec(af)) - @fact anew[1] --> a[1] - @fact ndims(anew) --> 0 + @fact anew[1] --> a[1] "test FLyINs" + @fact ndims(anew) --> 0 "test JQCoAo" anew = reinterpret(RGB{Float64}, af) - @fact anew --> a - @fact_throws ErrorException reinterpret(RGB{Float32}, af) + @fact anew --> a "test VU6f3n" + @fact_throws DimensionMismatch reinterpret(RGB{Float32}, af) "test 86GKXq" Au8 = rand(0x00:0xff, 3, 5, 4) A8 = reinterpret(UFixed8, Au8) rawrgb8 = reinterpret(RGB, A8) - @fact eltype(rawrgb8) --> RGB{UFixed8} - @fact reinterpret(UFixed8, rawrgb8) --> A8 - @fact reinterpret(UInt8, rawrgb8) --> Au8 + @fact eltype(rawrgb8) --> RGB{UFixed8} "test U5YnpG" + @fact reinterpret(UFixed8, rawrgb8) --> A8 "test VIsSBT" + @fact reinterpret(UInt8, rawrgb8) --> Au8 "test cWVSZ5" rawrgb32 = convert(Array{RGB{Float32}}, rawrgb8) - @fact eltype(rawrgb32) --> RGB{Float32} - @fact ufixed8(rawrgb32) --> rawrgb8 - @fact reinterpret(UFixed8, rawrgb8) --> A8 + @fact eltype(rawrgb32) --> RGB{Float32} "test BPX0oN" + @fact ufixed8(rawrgb32) --> rawrgb8 "test 10hrUB" + @fact reinterpret(UFixed8, rawrgb8) --> A8 "test xz6V7Y" imrgb8 = convert(Image, rawrgb8) - @fact spatialorder(imrgb8) --> Images.yx - @fact convert(Image, imrgb8) --> exactly(imrgb8) - @fact convert(Image{RGB{UFixed8}}, imrgb8) --> exactly(imrgb8) + @fact spatialorder(imrgb8) --> (Symbol.(Images.yx)...,) "test up03c2" + @fact convert(Image, imrgb8) --> exactly(imrgb8) "test CaRwd8" + @fact convert(Image{RGB{UFixed8}}, imrgb8) --> exactly(imrgb8) "test 39AZU3" im8 = reinterpret(UFixed8, imrgb8) - @fact data(im8) --> A8 - @fact permutedims(reinterpret(UFixed8, separate(imrgb8)), (3, 1, 2)) --> im8 - @fact reinterpret(UInt8, imrgb8) --> Au8 - @fact reinterpret(RGB, im8) --> imrgb8 + @fact data(im8) --> A8 "test lZSAH9" + @fact permutedims(ufixedview(U8, separate(imrgb8)), (3, 1, 2)) --> im8 "test zDOWZM" + @fact reinterpret(UInt8, imrgb8) --> Au8 "test HeezpR" + @fact reinterpret(RGB, im8) --> imrgb8 "test VJUpj3" ims8 = separate(imrgb8) - @fact colordim(ims8) --> 3 - @fact colorspace(ims8) --> "RGB" - @fact convert(Image, ims8) --> exactly(ims8) - @fact convert(Image{UFixed8}, ims8) --> exactly(ims8) - @fact separate(ims8) --> exactly(ims8) + @fact colordim(ims8) --> 0 "test nGifan" + @fact colorspace(ims8) --> "Gray" "test R0VFeL" + @fact convert(Image, ims8) --> exactly(ims8) "test EGoCYN" + @fact convert(Image{UFixed8}, ims8) --> exactly(ims8) "test Qly190" + @fact separate(ims8) --> exactly(ims8) "test hAxqus" A = reinterpret(UFixed8, UInt8[1 2; 3 4]) imgray = convert(Image{Gray{UFixed8}}, A) - @fact spatialorder(imgray) --> Images.yx - @fact data(imgray) --> reinterpret(Gray{UFixed8}, [0x01 0x02; 0x03 0x04]) - @fact eltype(convert(Image{HSV{Float32}}, imrgb8)) --> HSV{Float32} - @fact eltype(convert(Image{HSV}, float32(imrgb8))) --> HSV{Float32} + @fact spatialorder(imgray) --> (Symbol.(Images.yx)...,) "test gLMUOh" + @fact data(imgray) --> reinterpret(Gray{UFixed8}, [0x01 0x02; 0x03 0x04]) "test UC9NtZ" + @fact eltype(convert(Image{HSV{Float32}}, imrgb8)) --> HSV{Float32} "test cwCaVn" + @fact eltype(convert(Image{HSV}, float32(imrgb8))) --> HSV{Float32} "test VB4EU3" - @fact eltype(convert(Array{Gray}, imrgb8)) --> Gray{U8} - @fact eltype(convert(Image{Gray}, imrgb8)) --> Gray{U8} - @fact eltype(convert(Array{Gray}, data(imrgb8))) --> Gray{U8} - @fact eltype(convert(Image{Gray}, data(imrgb8))) --> Gray{U8} + @fact eltype(convert(Array{Gray}, imrgb8)) --> Gray{U8} "test 4UOxZh" + @fact eltype(convert(Image{Gray}, imrgb8)) --> Gray{U8} "test 2hhcQd" + @fact eltype(convert(Array{Gray}, data(imrgb8))) --> Gray{U8} "test SdEY95" + @fact eltype(convert(Image{Gray}, data(imrgb8))) --> Gray{U8} "test lzipLL" # Issue 232 let img = Image(reinterpret(Gray{UFixed16}, rand(UInt16, 5, 5))) imgs = subim(img, :, :) - @fact isa(minfinite(imgs), UFixed16) --> true + @fact isa(minfinite(imgs), Gray{UFixed16}) --> true "test PlxHep" # Raw imgdata = rand(UInt16, 5, 5) img = Image(reinterpret(Gray{UFixed16}, imgdata)) - @fact all(raw(img) .== imgdata) --> true - @fact typeof(raw(img)) --> Array{UInt16,2} - @fact typeof(raw(Image(rawrgb8))) --> Array{UInt8,3} # check color images - @fact size(raw(Image(rawrgb8))) --> (3,5,4) - @fact typeof(raw(imgdata)) --> typeof(imgdata) # check array fallback - @fact all(raw(imgdata) .== imgdata) --> true + @fact all(raw(img) .== imgdata) --> true "test EvOATF" + @fact typeof(raw(img).data) --> Array{UInt16,2} "test YlySCh" + @fact typeof(raw(Image(rawrgb8)).data) --> Array{UInt8,3} "test uOxsmv" # check color images + @fact size(raw(Image(rawrgb8))) --> (3,5,4) "test bM2K4C" + @fact typeof(raw(imgdata)) --> typeof(imgdata) "test 1Bf874" # check array fallback + @fact all(raw(imgdata) .== imgdata) --> true "test LJLDPq" end end end From febbbd94ee5409782b43a3a787df862975776b3a Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 18 Sep 2016 15:41:33 -0500 Subject: [PATCH 04/38] Get the map tests passing --- src/algorithms.jl | 5 ----- src/map.jl | 8 +++++--- test/old/map.jl | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index ad78890b..824dd945 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -185,11 +185,6 @@ function maxabsfinite{T}(A::AbstractArray{T}) ret end -# Issue #232. FIXME: really should return a Gray here? -for f in (:minfinite, :maxfinite, :maxabsfinite) - @eval $f{T}(A::AbstractArray{Gray{T}}) = $f(reinterpret(T, data(A))) -end - minfinite_scalar{T}(a::T, b::T) = isfinite(a) ? (b < a ? b : a) : b maxfinite_scalar{T}(a::T, b::T) = isfinite(a) ? (b > a ? b : a) : b minfinite_scalar{T<:Union{Integer,FixedPoint}}(a::T, b::T) = b < a ? b : a diff --git a/src/map.jl b/src/map.jl index fcc5f46a..781420bf 100644 --- a/src/map.jl +++ b/src/map.jl @@ -114,6 +114,7 @@ immutable BS{N} end _immap{T<:Unsigned,N}(::Type{T}, ::Type{BS{N}}, val::Unsigned) = (v = val>>>N; tm = oftype(val, typemax(T)); convert(T, ifelse(v > tm, tm, v))) _immap{T<:UFixed,N}(::Type{T}, ::Type{BS{N}}, val::UFixed) = reinterpret(T, _immap(FixedPointNumbers.rawtype(T), BS{N}, reinterpret(val))) immap{T<:Real,N}(mapi::BitShift{T,N}, val::Real) = _immap(T, BS{N}, val) +immap{T<:Real,N}(mapi::BitShift{T,N}, val::Gray) = _immap(T, BS{N}, val.val) immap{T<:Real,N}(mapi::BitShift{Gray{T},N}, val::Gray) = Gray(_immap(T, BS{N}, val.val)) map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::Unsigned) = _immap(UInt8, BS{N}, val) map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::UFixed) = _immap(UFixed8, BS{N}, val) @@ -251,7 +252,8 @@ ScaleMinMax{To,From<:Real}(::Type{To}, img::AbstractArray{Gray{From}}, mn::Real, ScaleMinMax{To,From<:Real,R<:Real}(::Type{To}, img::AbstractArray{From}, mn::Gray{R}, mx::Gray{R}) = ScaleMinMax(To, convert(From,mn.val), convert(From,mx.val), 1.0f0/(convert(Float32, convert(From,mx.val))-convert(Float32, convert(From,mn.val)))) ScaleMinMax{To,From<:Real,R<:Real}(::Type{To}, img::AbstractArray{Gray{From}}, mn::Gray{R}, mx::Gray{R}) = ScaleMinMax(To, convert(From,mn.val), convert(From,mx.val), 1.0f0/(convert(Float32, convert(From,mx.val))-convert(Float32, convert(From,mn.val)))) ScaleMinMax{To}(::Type{To}, img::AbstractArray) = ScaleMinMax(To, img, minfinite(img), maxfinite(img)) -ScaleMinMax{To,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = (imgr = reinterpret(eltype(CV), img); ScaleMinMax(To, minfinite(imgr), maxfinite(imgr))) +ScaleMinMax{To<:Real,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = (imgr = channelview(img); ScaleMinMax(To, minfinite(imgr), maxfinite(imgr))) +ScaleMinMax{To<:Colorant,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = (imgr = channelview(img); ScaleMinMax(To, minfinite(imgr), maxfinite(imgr))) similar{T,F,To,From,S}(mapi::ScaleMinMax{To,From,S}, ::Type{T}, ::Type{F}) = ScaleMinMax{T,F,S}(convert(F,mapi.min), convert(F.mapi.max), mapi.s) @@ -514,9 +516,9 @@ map!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexe function _mapindx!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexed{T1,N}) dimg = data(img) dout = data(out) - colmap = immap(mapi, img.cmap) + colmap = immap(mapi, dimg.values) for I in eachindex(dout, dimg) - @inbounds dout[I] = colmap[dimg[I]] + @inbounds dout[I] = colmap[dimg.index[I]] end out end diff --git a/test/old/map.jl b/test/old/map.jl index e6a902d4..cd9e8373 100644 --- a/test/old/map.jl +++ b/test/old/map.jl @@ -274,7 +274,7 @@ facts("Map") do gray = collect(linspace(0.0,1.0,5)) # a 1-dimensional image gray8 = [round(UInt8, 255 * x) for x in gray] gray32 = UInt32[convert(UInt32, g)<<16 | convert(UInt32, g)<<8 | convert(UInt32, g) for g in gray8] - imgray = Images.Image(gray, Dict{Compat.ASCIIString,Any}([("colordim",0), ("colorspace","Gray")])) + imgray = Images.Image(gray) buf = map(Images.mapinfo(UInt32, imgray), imgray) # Images.uint32color(imgray) @fact buf --> reinterpret(RGB24, gray32) rgb = RGB{Float64}[RGB(g, g, g) for g in gray] @@ -286,13 +286,13 @@ facts("Map") do buf = map(Images.mapinfo(UInt32, img), img) # Images.uint32color(img) @fact buf --> reinterpret(RGB24, gray32) rgb = repeat(gray, outer=[1,3]) - img = Images.Image(rgb, Dict{Compat.ASCIIString,Any}([("colordim",2), ("colorspace","RGB"), ("spatialorder",["x"])])) + img = colorview(RGB, permuteddimsview(rgb, (2,1))) buf = map(Images.mapinfo(UInt32, img), img) # Images.uint32color(img) @fact buf --> reinterpret(RGB24, gray32) g = green(img) @fact g --> gray rgb = repeat(gray', outer=[3,1]) - img = Images.Image(rgb, Dict{Compat.ASCIIString,Any}([("colordim",1), ("colorspace","RGB"), ("spatialorder",["x"])])) + img = Images.Image(colorview(RGB, rgb)) buf = map(Images.mapinfo(UInt32, img), img) # Images.uint32color(img) @fact buf --> reinterpret(RGB24, gray32) b = blue(img) From a3c4d38ff6800716e83a9685436aae78a8008585 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 18 Sep 2016 16:21:37 -0500 Subject: [PATCH 05/38] Get overlays tests passing --- test/old/overlays.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/old/overlays.jl b/test/old/overlays.jl index d7c66062..31fa93ca 100644 --- a/test/old/overlays.jl +++ b/test/old/overlays.jl @@ -53,7 +53,7 @@ facts("Overlay") do end context("Four") do - img1 = Images.Image(gray, Dict{Compat.ASCIIString, Any}([("colorspace", "Gray"), ("spatialorder",["x"])])) + img1 = Images.Image(gray) ovr = Images.OverlayImage((2gray, img1), (RGB{Float32}(1, 0, 1), RGB{Float32}(0, 1, 0)), ((0, 1),(0, 1))) @fact isa(ovr, Images.Image) --> true @fact haskey(ovr, "colorspace") --> false From bf576785f11d5e2eda0a5ba32057db550e073491 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 20 Sep 2016 02:46:37 -0500 Subject: [PATCH 06/38] Get the algorithms tests passing --- NEWS.md | 17 +- src/Images.jl | 25 +- src/algorithms.jl | 109 +++++---- src/deprecated.jl | 28 +++ test/REQUIRE | 1 + test/old/algorithms.jl | 508 ++++++++++++++--------------------------- 6 files changed, 281 insertions(+), 407 deletions(-) diff --git a/NEWS.md b/NEWS.md index 71afe67d..24da5942 100644 --- a/NEWS.md +++ b/NEWS.md @@ -60,7 +60,9 @@ Key changes (of which many are breaking): been deprecated and replaced with dispatch-leveraging alternatives. Specification of standard kernels has been changed considerably, and has been split out into two modules, `Kernel` and - `KernelFactors`, both defined in the `ImageFiltering` package. + `KernelFactors`, both defined in the `ImageFiltering` package. In + particular note the `IIRGaussian` types which contain the + functionality that was formerly in `imfilter_gaussian`. - Previous versions of Images used `reinterpret` for several operations, but `reinterpret` fails for most `AbstractArray`s other @@ -70,6 +72,19 @@ Key changes (of which many are breaking): is still used when applicable.) Consequently, this release features better support for a wider range of array types. +Other changes: + +- `extrema_filter` is being deprecated in favor of + `rankfilter(extrema, A, window)`. However, this returns an array of + `(min,max)` tuples rather than separate `min`, `max` arrays. This is + intended to transition towards a future API where one can pass `min` + or `max` in place of `extrema` to obtain just get one of + these. Currently, you can retrieve the `min` array with `first.(mm)` + and the `max` array with `last.(mm)`. + +- `extrema_filter` discards the edges of the image, whereas + `rankfilter` returns an array of the same size as the input. + # Older versions For earlier history, please see the git revision history. diff --git a/src/Images.jl b/src/Images.jl index 822e484a..e2c488e8 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -38,6 +38,7 @@ typealias TransparentRGB{C<:AbstractRGB,T} TransparentColor{C,T,4} typealias TransparentGray{C<:AbstractGray,T} TransparentColor{C,T,2} import Graphics import Graphics: width, height, Point +using StatsBase # TODO: eliminate this dependency # if isdefined(module_parent(Images), :Grid) # import ..Grid.restrict @@ -50,6 +51,8 @@ const is_little_endian = ENDIAN_BOM == 0x04030201 @reexport using ImageMetadata @reexport using ImageFiltering +using ImageMetadata: ImageMetaAxis + using Base.Cartesian # TODO: delete this include("map.jl") @@ -165,20 +168,11 @@ export # types ufixed8sc, ufixedsc, - # flip dimensions - flipx, - flipy, - flipz, - # algorithms - ando3, - ando4, - ando5, backdiffx, backdiffy, dilate, erode, - extrema_filter, opening, closing, tophat, @@ -187,8 +181,6 @@ export # types morpholaplace, forwarddiffx, forwarddiffy, - gaussian2d, - imaverage, imcorner, harris, shi_tomasi, @@ -196,22 +188,13 @@ export # types fastcorners, meancovs, gammacovs, - imdog, - imedge, - imfilter, - imfilter!, - imfilter_fft, - imfilter_gaussian, - imfilter_gaussian!, + imedge, # TODO: deprecate? imfilter_LoG, blob_LoG, findlocalmaxima, findlocalminima, imgaussiannoise, - imgradients, - imlaplacian, imlineardiffusion, - imlog, imROF, #Exposure diff --git a/src/algorithms.jl b/src/algorithms.jl index 824dd945..745c826f 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -1,3 +1,5 @@ +using Base: indices1, tail + Compat.@dep_vectorize_2arg Gray atan2 Compat.@dep_vectorize_2arg Gray hypot @@ -371,31 +373,31 @@ function blob_LoG{T,N}(img::AbstractArray{T,N}, sigmas) radii = sqrt(2.0)*sigmas maxima = findlocalmaxima(img_LoG, 1:ndims(img_LoG), (true, falses(N)...)) - [(img_LoG[x...], radii[x[1]], tail(x.I)...) for x in maxima] + [(img_LoG[x...], radii[x[1]], tail(x)...) for x in maxima] end findlocalextrema{T,N}(img::AbstractArray{T,N}, region, edges::Bool, order) = findlocalextrema(img, region, ntuple(d->edges,N), order) # FIXME -# @generated function findlocalextrema{T,N}(img::AbstractArray{T,N}, region::Union{Tuple{Int},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) -# quote -# issubset(region,1:ndims(img)) || throw(ArgumentError("Invalid region.")) -# extrema = Tuple{(@ntuple $N d->Int)...}[] -# @inbounds @nloops $N i d->((1+!edges[d]):(size(img,d)-!edges[d])) begin -# isextrema = true -# img_I = (@nref $N img i) -# @nloops $N j d->(in(d,region) ? (max(1,i_d-1):min(size(img,d),i_d+1)) : i_d) begin -# (@nall $N d->(j_d == i_d)) && continue -# if !Base.Order.lt(order, (@nref $N img j), img_I) -# isextrema = false -# break -# end -# end -# isextrema && push!(extrema, (@ntuple $N d->(i_d))) -# end -# extrema -# end -# end +@generated function findlocalextrema{T,N}(img::AbstractArray{T,N}, region::Union{Tuple{Int,Vararg{Int}},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) + quote + issubset(region,1:ndims(img)) || throw(ArgumentError("Invalid region.")) + extrema = Tuple{(@ntuple $N d->Int)...}[] + @inbounds @nloops $N i d->((1+!edges[d]):(size(img,d)-!edges[d])) begin + isextrema = true + img_I = (@nref $N img i) + @nloops $N j d->(in(d,region) ? (max(1,i_d-1):min(size(img,d),i_d+1)) : i_d) begin + (@nall $N d->(j_d == i_d)) && continue + if !Base.Order.lt(order, (@nref $N img j), img_I) + isextrema = false + break + end + end + isextrema && push!(extrema, (@ntuple $N d->(i_d))) + end + extrema + end +end """ `findlocalmaxima(img, [region, edges]) -> Vector{Tuple}` @@ -511,41 +513,54 @@ along the dimensions listed in `region`, or all spatial coordinates if `region` is not specified. It anti-aliases the image as it goes, so is better than a naive summation over 2x2 blocks. """ -restrict(img::AbstractImageDirect, ::Tuple{}) = img +restrict(img::AbstractArray, ::Tuple{}) = img -function restrict(img::AbstractImageDirect, region::Union{Dims, Vector{Int}}=coords_spatial(img)) - A = data(img) - for dim in region - A = _restrict(A, dim) - end - props = copy(properties(img)) - ps = copy(pixelspacing(img)) - ind = findin(coords_spatial(img), region) - ps[ind] *= 2 - props["pixelspacing"] = ps - Image(A, props) +typealias RegionType Union{Dims,Vector{Int}} + +function restrict(img::ImageMeta, region::RegionType=coords_spatial(img)) + shareproperties(img, restrict(data(img), region)) +end + +function restrict{T,N}(img::AxisArray{T,N}, region::RegionType=coords_spatial(img)) + inregion = falses(ndims(img)) + inregion[[region...]] = true + inregiont = (inregion...,)::NTuple{N,Bool} + AxisArray(restrict(img.data, region), map(modax, axes(img), inregiont)) +end + +function restrict{Ax}(img::Union{AxisArray,ImageMetaAxis}, ::Type{Ax}) + A = _restrict(img.data, axisdim(img, Ax)) + AxisArray(A, replace_axis(modax(img[Ax]), axes(img))) +end + +replace_axis(newax, axs) = _replace_axis(newax, axnametype(newax), axs...) +@inline _replace_axis{Ax}(newax, ::Type{Ax}, ax::Ax, axs...) = (newax, _replace_axis(newax, Ax, axs...)...) +@inline _replace_axis{Ax}(newax, ::Type{Ax}, ax, axs...) = (ax, _replace_axis(newax, Ax, axs...)...) +_replace_axis{Ax}(newax, ::Type{Ax}) = () + +axnametype{name}(ax::Axis{name}) = Axis{name} + +function modax(ax) + v = ax.val + veven = v[1]-step(v)/2 : 2*step(v) : v[end]+step(v)/2 + return ax(isodd(length(v)) ? oftype(veven, v[1:2:end]) : veven) end -function restrict(A::AbstractArray, region::Union{Dims, Vector{Int}}=coords_spatial(A)) + +modax(ax, inregion::Bool) = inregion ? modax(ax) : ax + +function restrict(A::AbstractArray, region::RegionType=coords_spatial(A)) for dim in region A = _restrict(A, dim) end A end -function restrict{S<:String}(img::AbstractImageDirect, region::Union{Tuple{Vararg{String}}, Vector{S}}) - so = spatialorder(img) - regioni = Int[] - for i = 1:length(region) - push!(regioni, require_dimindex(img, region[i], so)) - end - restrict(img, regioni) -end - -function _restrict(A, dim) +function _restrict{T,N}(A::AbstractArray{T,N}, dim) if size(A, dim) <= 2 return A end - out = Array(typeof(A[1]/4+A[2]/2), ntuple(i->i==dim?restrict_size(size(A,dim)):size(A,i), ndims(A))) + newsz = ntuple(i->i==dim?restrict_size(size(A,dim)):size(A,i), Val{N}) + out = Array{typeof(A[1]/4+A[2]/2)}(newsz) restrict!(out, A, dim) out end @@ -801,17 +816,17 @@ function extremefilt!(A::AbstractArray, select::Function, region=coords_spatial( end @noinline function _extremefilt!(A, select, Rpre, inds, Rpost) - # This is not cache efficient, unfortunately + # TODO: improve the cache efficiency for Ipost in Rpost, Ipre in Rpre # first element along dim i1 = first(inds) a2, a3 = A[Ipre, i1, Ipost], A[Ipre, i1+1, Ipost] A[Ipre, i1, Ipost] = select(a2, a3) # interior along dim - for i = i1+2:last(inds)-1 + for i = i1+2:last(inds) a1, a2 = a2, a3 a3 = A[Ipre, i, Ipost] - A[Ipre, i, Ipost] = select(select(a1, a2), a3) + A[Ipre, i-1, Ipost] = select(select(a1, a2), a3) end # last element along dim A[Ipre, last(inds), Ipost] = select(a2, a3) diff --git a/src/deprecated.jl b/src/deprecated.jl index bd162d47..2cbaf05a 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -4,3 +4,31 @@ const xy = ["x", "y"] SliceData(args...) = error("SliceData has been removed, please use julia's regular indexing operations") reslice!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") rerange!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") + +@deprecate flipx(img) flipdim(img, 2) +@deprecate flipy(img) flipdim(img, 1) +@deprecate flipz(img) flipdim(img, 3) + +@deprecate ando3 KernelFactors.ando3 +@deprecate ando4 KernelFactors.ando3 +@deprecate ando5 KernelFactors.ando3 +@deprecate gaussian2d KernelFactors.gaussian +@deprecate imaverage KernelFactors.boxcar +@deprecate imdog Kernel.DoG +@deprecate imlog Kernel.LoG +@deprecate imlaplacian Kernel.Laplacian + +@deprecate extremefilt!(A::AbstractArray, ::Base.Order.ForwardOrdering, region=coords_spatial(A)) extremefilt!(A, max, region) +@deprecate extremefilt!(A::AbstractArray, ::Base.Order.ReverseOrdering, region=coords_spatial(A)) extremefilt!(A, min, region) +@deprecate extremefilt!{C<:AbstractRGB}(A::AbstractArray{C}, ::Base.Order.ForwardOrdering, region=coords_spatial(A)) extremefilt!(A, (x,y)->mapc(max,x,y), region) +@deprecate extremefilt!{C<:AbstractRGB}(A::AbstractArray{C}, ::Base.Order.ReverseOrdering, region=coords_spatial(A)) extremefilt!(A, (x,y)->mapc(min,x,y), region) + +function restrict{S<:String}(img::AbstractArray, region::Union{Tuple{String,Vararg{String}}, Vector{S}}) + depwarn("restrict(img, strings) is deprecated, please use restrict(img, axes) with an AxisArray", :restrict) + so = spatialorder(img) + regioni = Int[] + for i = 1:length(region) + push!(regioni, require_dimindex(img, region[i], so)) + end + restrict(img, regioni) +end diff --git a/test/REQUIRE b/test/REQUIRE index 84afd7f4..5147a11a 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -3,3 +3,4 @@ FactCheck @windows ImageMagick @linux ImageMagick @osx QuartzImageIO +OffsetArrays \ No newline at end of file diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index 52261336..13c7bbcc 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -1,4 +1,4 @@ -using FactCheck, Base.Test, Images, Colors, FixedPointNumbers +using FactCheck, Base.Test, Images, Colors, FixedPointNumbers, Compat, OffsetArrays using Compat.view srand(1234) @@ -8,41 +8,25 @@ facts("Algorithms") do approx_equal(ar, v) = @compat all(abs.(ar.-v) .< sqrt(eps(v))) approx_equal(ar::Images.AbstractImage, v) = approx_equal(Images.data(ar), v) - context("Flip dimensions") do - A = UInt8[200 150; 50 1] - img_x = grayim(A) - img_y = permutedims(img_x, [2, 1]) - - @fact raw(flipdim(img_x, "x")) --> raw(flipdim(img_x, 1)) - @fact raw(flipdim(img_x, "x")) --> flipdim(A, 1) - @fact raw(flipdim(img_y, "x")) --> raw(flipdim(img_y, 2)) - @fact raw(flipdim(img_y, "x")) --> flipdim(A', 2) - - @fact raw(flipdim(img_x, "y")) --> raw(flipdim(img_x, 2)) - @fact raw(flipdim(img_x, "y")) --> flipdim(A, 2) - @fact raw(flipdim(img_y, "y")) --> raw(flipdim(img_y, 1)) - @fact raw(flipdim(img_y, "y")) --> flipdim(A', 1) - - @fact raw(flipx(img_x)) --> raw(flipdim(img_x, "x")) - @fact raw(flipx(img_y)) --> raw(flipdim(img_y, "x")) - - @fact raw(flipy(img_x)) --> raw(flipdim(img_x, "y")) - @fact raw(flipy(img_y)) --> raw(flipdim(img_y, "y")) - end + context("Flip dimensions") do + A = grayim(UInt8[200 150; 50 1]) + @fact raw(flipy(A)) --> raw(flipdim(A, 1)) "test vRs7Gx" + @fact raw(flipx(A)) --> raw(flipdim(A, 2)) "test omDVHe" + end context("Arithmetic") do img = convert(Images.Image, zeros(3,3)) img2 = (img .+ 3)/2 - @fact all(img2 .== 1.5) --> true + @fact all(img2 .== 1.5) --> true "test mzgz4D" img3 = 2img2 - @fact all(img3 .== 3) --> true + @fact all(img3 .== 3) --> true "test ECrt7w" img3 = copy(img2) img3[img2 .< 4] = -1 - @fact all(img3 .== -1) --> true + @fact all(img3 .== -1) --> true "test 5OclVr" img = convert(Images.Image, rand(3,4)) A = rand(3,4) img2 = img .* A - @fact all(Images.data(img2) == Images.data(img).*A) --> true + @fact all(Images.data(img2) == Images.data(img).*A) --> true "test Txfm6a" img2 = convert(Images.Image, A) img2 = img2 .- 0.5 img3 = 2img .* img2 @@ -51,346 +35,194 @@ facts("Algorithms") do # Same operations with Color images img = Images.colorim(zeros(Float32,3,4,5)) img2 = (img .+ RGB{Float32}(1,1,1))/2 - @fact all(img2 .== RGB{Float32}(1,1,1)/2) --> true + @fact all(img2 .== RGB{Float32}(1,1,1)/2) --> true "test yBGjhP" img3 = 2img2 - @fact all(img3 .== RGB{Float32}(1,1,1)) --> true + @fact all(img3 .== RGB{Float32}(1,1,1)) --> true "test ZaNzgJ" A = fill(2, 4, 5) - @fact all(A.*img2 .== fill(RGB{Float32}(1,1,1), 4, 5)) --> true + @fact all(A.*img2 .== fill(RGB{Float32}(1,1,1), 4, 5)) --> true "test OT1P7V" img2 = img2 .- RGB{Float32}(1,1,1)/2 A = rand(UInt8,3,4) img = reinterpret(Gray{UFixed8}, Images.grayim(A)) imgm = mean(img) imgn = img/imgm - @fact reinterpret(Float64, Images.data(imgn)) --> roughly(convert(Array{Float64}, A/mean(A))) - @fact imcomplement([Gray(0.2)]) --> [Gray(0.8)] - @fact imcomplement([Gray{U8}(0.2)]) --> [Gray{U8}(0.8)] - @fact imcomplement([RGB(0,0.3,1)]) --> [RGB(1,0.7,0)] - @fact imcomplement([RGBA(0,0.3,1,0.7)]) --> [RGBA(1.0,0.7,0.0,0.7)] - @fact imcomplement([RGBA{U8}(0,0.6,1,0.7)]) --> [RGBA{U8}(1.0,0.4,0.0,0.7)] + @fact reinterpret(Float64, Images.data(imgn)) --> roughly(convert(Array{Float64}, A/mean(A))) "test NTAvmj" + @fact imcomplement([Gray(0.2)]) --> [Gray(0.8)] "test Gu9b6h" + @fact imcomplement([Gray{U8}(0.2)]) --> [Gray{U8}(0.8)] "test Kle9oP" + @fact imcomplement([RGB(0,0.3,1)]) --> [RGB(1,0.7,0)] "test jgxQxd" + @fact imcomplement([RGBA(0,0.3,1,0.7)]) --> [RGBA(1.0,0.7,0.0,0.7)] "test tEAo1x" + @fact imcomplement([RGBA{U8}(0,0.6,1,0.7)]) --> [RGBA{U8}(1.0,0.4,0.0,0.7)] "test MGiodE" img = rand(1:10,10,10) img2 = rand(1:2,10,10) img3 = reinterpret(Gray{U8}, grayim(rand(UInt8,10,10))) - @fact all([entropy(img, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true - @fact all([entropy(img2, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true - @fact all([entropy(img3, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true + @fact all([entropy(img, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test lwgD8l" + @fact all([entropy(img2, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test L7wFfa" + @fact all([entropy(img3, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test OHwUzO" end context("Reductions") do A = rand(5,5,3) img = Images.colorim(A, "RGB") s12 = sum(img, (1,2)) - @fact Images.colorspace(s12) --> "RGB" - s3 = sum(img, (3,)) - @fact Images.colorspace(s3) --> "Unknown" + @fact Images.colorspace(s12) --> "RGB" "test gjp91q" A = [NaN, 1, 2, 3] - @fact Images.meanfinite(A, 1) --> roughly([2]) + @fact Images.meanfinite(A, 1) --> roughly([2]) "test jy9vvu" A = [NaN 1 2 3; NaN 6 5 4] @test_approx_eq Images.meanfinite(A, 1) [NaN 3.5 3.5 3.5] @test_approx_eq Images.meanfinite(A, 2) [2, 5]' @test_approx_eq Images.meanfinite(A, (1,2)) [3.5] - @fact Images.minfinite(A) --> 1 - @fact Images.maxfinite(A) --> 6 - @fact Images.maxabsfinite(A) --> 6 + @fact Images.minfinite(A) --> 1 "test ctthKJ" + @fact Images.maxfinite(A) --> 6 "test 1TSlCM" + @fact Images.maxabsfinite(A) --> 6 "test w7j5sr" A = rand(10:20, 5, 5) - @fact minfinite(A) --> minimum(A) - @fact maxfinite(A) --> maximum(A) + @fact minfinite(A) --> minimum(A) "test k5GtIe" + @fact maxfinite(A) --> maximum(A) "test 8qaTAa" A = reinterpret(UFixed8, rand(0x00:0xff, 5, 5)) - @fact minfinite(A) --> minimum(A) - @fact maxfinite(A) --> maximum(A) + @fact minfinite(A) --> minimum(A) "test RCl2VS" + @fact maxfinite(A) --> maximum(A) "test eKwX2u" A = rand(Float32,3,5,5) img = Images.colorim(A, "RGB") dc = Images.data(Images.meanfinite(img, 1))-reinterpret(RGB{Float32}, mean(A, 2), (1,5)) - @fact maximum(map(abs, dc)) --> less_than(1e-6) + @fact maximum(map(abs, dc)) --> less_than(1e-6) "test e62u7Q" dc = Images.minfinite(img)-RGB{Float32}(minimum(A, (2,3))...) - @fact abs(dc) --> less_than(1e-6) + @fact abs(dc) --> less_than(1e-6) "test ynXLYd" dc = Images.maxfinite(img)-RGB{Float32}(maximum(A, (2,3))...) - @fact abs(dc) --> less_than(1e-6) + @fact abs(dc) --> less_than(1e-6) "test Nu5oAt" a = convert(Array{UInt8}, [1, 1, 1]) b = convert(Array{UInt8}, [134, 252, 4]) - @fact Images.sad(a, b) --> 387 - @fact Images.ssd(a, b) --> 80699 + @fact Images.sad(a, b) --> 387 "test sx70B8" + @fact Images.ssd(a, b) --> 80699 "test aFz7hO" af = reinterpret(UFixed8, a) bf = reinterpret(UFixed8, b) - @fact Images.sad(af, bf) --> roughly(387f0/255) - @fact Images.ssd(af, bf) --> roughly(80699f0/255^2) + @fact Images.sad(af, bf) --> roughly(387f0/255) "test R3U9a6" + @fact Images.ssd(af, bf) --> roughly(80699f0/255^2) "test WtPNxa" ac = reinterpret(RGB{UFixed8}, a) bc = reinterpret(RGB{UFixed8}, b) - @fact Images.sad(ac, bc) --> roughly(387f0/255) - @fact Images.ssd(ac, bc) --> roughly(80699f0/255^2) + @fact Images.sad(ac, bc) --> roughly(387f0/255) "test wtRHsd" + @fact Images.ssd(ac, bc) --> roughly(80699f0/255^2) "test Ti1QN0" ag = reinterpret(RGB{UFixed8}, a) bg = reinterpret(RGB{UFixed8}, b) - @fact Images.sad(ag, bg) --> roughly(387f0/255) - @fact Images.ssd(ag, bg) --> roughly(80699f0/255^2) + @fact Images.sad(ag, bg) --> roughly(387f0/255) "test jaMtWn" + @fact Images.ssd(ag, bg) --> roughly(80699f0/255^2) "test Gc9gbr" a = rand(15,15) - @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a rand(13,15) [1,1] 0.01) - @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a rand(15,15) [1,1] 0.01) - @fact (Images.@test_approx_eq_sigma_eps a a [1,1] 0.01) --> nothing - @fact (Images.@test_approx_eq_sigma_eps a a+0.01*rand(size(a)) [1,1] 0.01) --> nothing - @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a a+0.5*rand(size(a)) [1,1] 0.01) + @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a rand(13,15) [1,1] 0.01) "test Pkx94Y" + @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a rand(15,15) [1,1] 0.01) "test xrZo3m" + @fact (Images.@test_approx_eq_sigma_eps a a [1,1] 0.01) --> nothing "test ccbApu" + @fact (Images.@test_approx_eq_sigma_eps a a+0.01*rand(size(a)) [1,1] 0.01) --> nothing "test JhUXjB" + @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a a+0.5*rand(size(a)) [1,1] 0.01) "test a1yPd4" a = colorim(rand(3,15,15)) - @fact (Images.@test_approx_eq_sigma_eps a a [1,1] 0.01) --> nothing - @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a colorim(rand(3,15,15)) [1,1] 0.01) + @fact (Images.@test_approx_eq_sigma_eps a a [1,1] 0.01) --> nothing "test 9DBljW" + @fact_throws ErrorException (Images.@test_approx_eq_sigma_eps a colorim(rand(3,15,15)) [1,1] 0.01) "test 4LhXLK" a = rand(15,15) - @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(13,15), [1,1], 0.01) - @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(15,15), [1,1], 0.01) - @fact Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) --> 0.0 - @fact Images.test_approx_eq_sigma_eps(a, a+0.01*rand(size(a)), [1,1], 0.01) --> greater_than(0.0) - @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, a+0.5*rand(size(a)), [1,1], 0.01) + @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(13,15), [1,1], 0.01) "test vMzGQH" + @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, rand(15,15), [1,1], 0.01) "test w51zmO" + @fact Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) --> 0.0 "test 8NfaXn" + @fact Images.test_approx_eq_sigma_eps(a, a+0.01*rand(size(a)), [1,1], 0.01) --> greater_than(0.0) "test S9POMO" + @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, a+0.5*rand(size(a)), [1,1], 0.01) "test SaFtHr" a = colorim(rand(3,15,15)) - @fact Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) --> 0.0 - @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, colorim(rand(3,15,15)), [1,1], 0.01) + @fact Images.test_approx_eq_sigma_eps(a, a, [1,1], 0.01) --> 0.0 "test wtlfUO" + @fact_throws ErrorException Images.test_approx_eq_sigma_eps(a, colorim(rand(3,15,15)), [1,1], 0.01) "test bccntj" - @fact Images.test_approx_eq_sigma_eps(a[:,1:end-1], a[1:end-1,:], [3,3], 0.1) --> less_than(0.1) - @fact_throws Images.test_approx_eq_sigma_eps(a[:,1:end-1], a[1:end-1,:], [3,3], 0.01) + @fact Images.test_approx_eq_sigma_eps(a[:,1:end-1], a[1:end-1,:], [3,3], 0.1) --> less_than(0.1) "test j0aGQD" + @fact_throws Images.test_approx_eq_sigma_eps(a[:,1:end-1], a[1:end-1,:], [3,3], 0.01) "test qJcPdD" a = zeros(10, 10) int_img = integral_image(a) - @fact all(int_img == a) --> true + @fact all(int_img == a) --> true "test UjtTze" a = ones(10,10) int_img = integral_image(a) chk = Array(1:10) - @fact all([vec(int_img[i, :]) == chk * i for i in 1:10]) --> true + @fact all([vec(int_img[i, :]) == chk * i for i in 1:10]) --> true "test TWnkpp" int_sum = boxdiff(int_img, 1, 1, 5, 2) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test CNwchG" int_sum = boxdiff(int_img, 1:5, 1:2) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test sbCuZQ" int_sum = boxdiff(int_img, CartesianIndex((1, 1)), CartesianIndex((5, 2))) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test UWb90L" int_sum = boxdiff(int_img, 1, 1, 2, 5) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test C4yK8H" int_sum = boxdiff(int_img, 1:2, 1:5) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test 9mTOGN" int_sum = boxdiff(int_img, CartesianIndex((1, 1)), CartesianIndex((2, 5))) - @fact int_sum --> 10.0 + @fact int_sum --> 10.0 "test 6B1IU2" int_sum = boxdiff(int_img, 4, 4, 8, 8) - @fact int_sum --> 25.0 + @fact int_sum --> 25.0 "test 4BiU7v" int_sum = boxdiff(int_img, 4:8, 4:8) - @fact int_sum --> 25.0 + @fact int_sum --> 25.0 "test GGj1cU" int_sum = boxdiff(int_img, CartesianIndex((4, 4)), CartesianIndex((8, 8))) - @fact int_sum --> 25.0 + @fact int_sum --> 25.0 "test vM8fF1" a = reshape(1:100, 10, 10) int_img = integral_image(a) - @fact int_img[diagind(int_img)] == Array([1, 26, 108, 280, 575, 1026, 1666, 2528, 3645, 5050]) --> true + @fact int_img[diagind(int_img)] == Array([1, 26, 108, 280, 575, 1026, 1666, 2528, 3645, 5050]) --> true "test oPHNmm" int_sum = boxdiff(int_img, 1, 1, 3, 3) - @fact int_sum --> 108 + @fact int_sum --> 108 "test eGx0PK" int_sum = boxdiff(int_img, 1:3, 1:3) - @fact int_sum --> 108 + @fact int_sum --> 108 "test KKhF8q" int_sum = boxdiff(int_img, CartesianIndex((1, 1)), CartesianIndex((3, 3))) - @fact int_sum --> 108 + @fact int_sum --> 108 "test K5Rnhs" int_sum = boxdiff(int_img, 1, 1, 5, 2) - @fact int_sum --> 80 + @fact int_sum --> 80 "test HM7SD3" int_sum = boxdiff(int_img, 1:5, 1:2) - @fact int_sum --> 80 + @fact int_sum --> 80 "test fBsFaz" int_sum = boxdiff(int_img, CartesianIndex((1, 1)), CartesianIndex((5, 2))) - @fact int_sum --> 80 + @fact int_sum --> 80 "test MhkNgv" int_sum = boxdiff(int_img, 4, 4, 8, 8) - @fact int_sum --> 1400 + @fact int_sum --> 1400 "test 3s3WYt" int_sum = boxdiff(int_img, 4:8, 4:8) - @fact int_sum --> 1400 + @fact int_sum --> 1400 "test uYHL4A" int_sum = boxdiff(int_img, CartesianIndex((4, 4)), CartesianIndex((8, 8))) - @fact int_sum --> 1400 + @fact int_sum --> 1400 "test V3Bw7v" img = zeros(40, 40) img[10:30, 10:30] = 1 pyramid = gaussian_pyramid(img, 3, 2, 1.0) - @fact size(pyramid[1]) == (40, 40) --> true - @fact size(pyramid[2]) == (20, 20) --> true - @fact size(pyramid[3]) == (10, 10) --> true - @fact size(pyramid[4]) == (5, 5) --> true - @fact isapprox(pyramid[1][20, 20], 1.0, atol = 0.01) --> true - @fact isapprox(pyramid[2][10, 10], 1.0, atol = 0.01) --> true - @fact isapprox(pyramid[3][5, 5], 1.0, atol = 0.05) --> true - @fact isapprox(pyramid[4][3, 3], 0.9, atol = 0.025) --> true + @fact size(pyramid[1]) == (40, 40) --> true "test 7PkJim" + @fact size(pyramid[2]) == (20, 20) --> true "test sFshCA" + @fact size(pyramid[3]) == (10, 10) --> true "test GoaiPs" + @fact size(pyramid[4]) == (5, 5) --> true "test 7x7aQ3" + @fact isapprox(pyramid[1][20, 20], 1.0, atol = 0.01) --> true "test 87udZ1" + @fact isapprox(pyramid[2][10, 10], 1.0, atol = 0.01) --> true "test HgqMZk" + @fact isapprox(pyramid[3][5, 5], 1.0, atol = 0.05) --> true "test 98smZ2" + @fact isapprox(pyramid[4][3, 3], 0.9, atol = 0.025) --> true "test NXNu7G" for p in pyramid h, w = size(p) - @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[1, :]]) --> true - @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[:, 1]]) --> true - @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[h, :]]) --> true - @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[:, w]]) --> true + @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[1, :]]) --> true "test n8h7aF" + @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[:, 1]]) --> true "test EMA9Jn" + @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[h, :]]) --> true "test cG3BfH" + @fact all(Bool[isapprox(v, 0, atol = 0.06) for v in p[:, w]]) --> true "test LcETLO" end end context("fft and ifft") do A = rand(Float32, 3, 5, 6) img = Images.colorim(A) - imgfft = fft(img) - @fact Images.data(imgfft) --> roughly(fft(A, 2:3)) - @fact Images.colordim(imgfft) --> 1 - img2 = ifft(imgfft) - @fact img2 --> roughly(reinterpret(Float32, img)) - end - - context("Array padding") do - A = [1 2; 3 4] - @fact Images.padindexes(A, 1, 0, 0, "replicate") --> [1,2] - @fact Images.padindexes(A, 1, 1, 0, "replicate") --> [1,1,2] - @fact Images.padindexes(A, 1, 0, 1, "replicate") --> [1,2,2] - @fact Images.padarray(A, (0,0), (0,0), "replicate") --> A - @fact Images.padarray(A, (1,2), (2,0), "replicate") --> [1 1 1 2; 1 1 1 2; 3 3 3 4; 3 3 3 4; 3 3 3 4] - @fact Images.padindexes(A, 1, 1, 0, "circular") --> [2,1,2] - @fact Images.padindexes(A, 1, 0, 1, "circular") --> [1,2,1] - @fact Images.padarray(A, [2,1], [0,2], "circular") --> [2 1 2 1 2; 4 3 4 3 4; 2 1 2 1 2; 4 3 4 3 4] - @fact Images.padindexes(A, 1, 1, 0, "symmetric") --> [1,1,2] - @fact Images.padindexes(A, 1, 0, 1, "symmetric") --> [1,2,2] - @fact Images.padarray(A, (1,2), (2,0), "symmetric") --> [2 1 1 2; 2 1 1 2; 4 3 3 4; 4 3 3 4; 2 1 1 2] - @fact Images.padarray(A, (1,2), (2,0), "value", -1) --> [-1 -1 -1 -1; -1 -1 1 2; -1 -1 3 4; -1 -1 -1 -1; -1 -1 -1 -1] - A = [1 2 3; 4 5 6] - @fact Images.padindexes(A, 2, 1, 0, "reflect") --> [2,1,2,3] - @fact Images.padindexes(A, 2, 0, 1, "reflect") --> [1,2,3,2] - @fact Images.padarray(A, (1,2), (2,0), "reflect") --> [6 5 4 5 6; 3 2 1 2 3; 6 5 4 5 6; 3 2 1 2 3; 6 5 4 5 6] - A = [1 2; 3 4] - @fact Images.padarray(A, (1,1)) --> [1 1 2 2; 1 1 2 2; 3 3 4 4; 3 3 4 4] - @fact Images.padarray(A, (1,1), "replicate", "both") --> [1 1 2 2; 1 1 2 2; 3 3 4 4; 3 3 4 4] - @fact Images.padarray(A, (1,1), "circular", "pre") --> [4 3 4; 2 1 2; 4 3 4] - @fact Images.padarray(A, (1,1), "symmetric", "post") --> [1 2 2; 3 4 4; 3 4 4] - A = ["a" "b"; "c" "d"] - @fact Images.padarray(A, (1,1)) --> ["a" "a" "b" "b"; "a" "a" "b" "b"; "c" "c" "d" "d"; "c" "c" "d" "d"] - @fact_throws ErrorException Images.padindexes(A, 1, 1, 1, "unknown") - @fact_throws ErrorException Images.padarray(A, (1,1), "unknown") - # issue #292 - A = trues(3,3) - @fact typeof(Images.padarray(A, (1,2), (2,1), "replicate")) --> BitArray{2} - @fact typeof(Images.padarray(Images.grayim(A), (1,2), (2,1), "replicate")) --> BitArray{2} - # issue #525 - A = falses(10,10,10) - B = view(A,1:8,1:8,1:8) - @fact isa(padarray(A, ones(Int,3), ones(Int,3), "replicate"), BitArray{3}) --> true - @fact isa(padarray(B, ones(Int,3), ones(Int,3), "replicate"), BitArray{3}) --> true - end - - @compat context("Filtering") do - EPS = 10*eps(float(UFixed8(1))) - imgcol = Images.colorim(rand(3,5,6)) - imgcolf = convert(Images.Image{RGB{UFixed8}}, imgcol) - for T in (Float64, Int) - A = zeros(T,3,3); A[2,2] = 1 - kern = rand(3,3) - @fact maximum(abs.(Images.imfilter(A, kern) - rot180(kern))) --> less_than(EPS) - kern = rand(2,3) - @fact maximum(abs.(Images.imfilter(A, kern)[1:2,:] - rot180(kern))) --> less_than(EPS) - kern = rand(3,2) - @fact maximum(abs.(Images.imfilter(A, kern)[:,1:2] - rot180(kern))) --> less_than(EPS) - end - kern = zeros(3,3); kern[2,2] = 1 - @fact maximum(map(abs, imgcol - Images.imfilter(imgcol, kern))) --> less_than(EPS) - @fact maximum(map(abs, imgcolf - Images.imfilter(imgcolf, kern))) --> less_than(EPS) - for T in (Float64, Int) - # Separable kernels - A = zeros(T,3,3); A[2,2] = 1 - kern = rand(3).*rand(3)' - @fact maximum(abs.(Images.imfilter(A, kern) - rot180(kern))) --> less_than(EPS) - kern = rand(2).*rand(3)' - @fact maximum(abs.(Images.imfilter(A, kern)[1:2,:] - rot180(kern))) --> less_than(EPS) - kern = rand(3).*rand(2)' - @fact maximum(abs.(Images.imfilter(A, kern)[:,1:2] - rot180(kern))) --> less_than(EPS) - end - A = zeros(3,3); A[2,2] = 1 - kern = rand(3,3) - @fact maximum(abs.(Images.imfilter_fft(A, kern) - rot180(kern))) --> less_than(EPS) - kern = rand(2,3) - @fact maximum(abs.(Images.imfilter_fft(A, kern)[1:2,:] - rot180(kern))) --> less_than(EPS) - kern = rand(3,2) - @fact maximum(abs.(Images.imfilter_fft(A, kern)[:,1:2] - rot180(kern))) --> less_than(EPS) - kern = zeros(3,3); kern[2,2] = 1 - @fact maximum(map(abs, imgcol - Images.imfilter_fft(imgcol, kern))) --> less_than(EPS) - @fact maximum(map(abs, imgcolf - Images.imfilter_fft(imgcolf, kern))) --> less_than(EPS) - - @fact approx_equal(Images.imfilter(ones(4,4), ones(3,3)), 9.0) --> true - @fact approx_equal(Images.imfilter(ones(3,3), ones(3,3)), 9.0) --> true - @fact approx_equal(Images.imfilter(ones(3,3), [1 1 1;1 0.0 1;1 1 1]), 8.0) --> true - img = convert(Images.Image, ones(4,4)) - @fact approx_equal(Images.imfilter(img, ones(3,3)), 9.0) --> true - A = zeros(5,5,3); A[3,3,[1,3]] = 1 - @fact Images.colordim(A) --> 3 - kern = rand(3,3) - kernpad = zeros(5,5); kernpad[2:4,2:4] = kern - Af = Images.imfilter(A, kern) - - @fact cat(3, rot180(kernpad), zeros(5,5), rot180(kernpad)) --> roughly(Af) - Aimg = permutedims(convert(Images.Image, A), [3,1,2]) - @fact Images.imfilter(Aimg, kern) --> roughly(permutedims(Af, [3,1,2])) - @fact approx_equal(Images.imfilter(ones(4,4),ones(1,3),"replicate"), 3.0) --> true - - A = zeros(5,5); A[3,3] = 1 - kern = rand(3,3) - Af = Images.imfilter(A, kern, "inner") - @fact Af --> rot180(kern) - Afft = Images.imfilter_fft(A, kern, "inner") - @fact Af --> roughly(Afft) - h = [0.24,0.87] - hfft = Images.imfilter_fft(eye(3), h, "inner") - hfft[abs.(hfft) .< 3eps()] = 0 - @fact Images.imfilter(eye(3), h, "inner") --> roughly(hfft) # issue #204 - - # circular - A = zeros(3, 3) - A[3,2] = 1 - kern = rand(3,3) - @fact Images.imfilter_fft(A, kern, "circular") --> roughly(kern[[1,3,2],[3,2,1]]) - - A = zeros(5, 5) - A[5,3] = 1 - kern = rand(3,3) - @fact Images.imfilter_fft(A, kern, "circular")[[1,4,5],2:4] --> roughly(kern[[1,3,2],[3,2,1]]) - - A = zeros(5, 5) - A[5,3] = 1 - kern = rand(3,3) - @fact Images.imfilter(A, kern, "circular")[[1,4,5],2:4] --> roughly(kern[[1,3,2],[3,2,1]]) - - @fact approx_equal(Images.imfilter_gaussian(ones(4,4), [5,5]), 1.0) --> true - A = fill(convert(Float32, NaN), 3, 3) - A[1,1] = 1 - A[2,1] = 2 - A[3,1] = 3 - @fact Images.imfilter_gaussian(A, [0,0]) --> exactly(A) - @test_approx_eq Images.imfilter_gaussian(A, [0,3]) A - B = copy(A) - B[isfinite.(B)] = 2 - @test_approx_eq Images.imfilter_gaussian(A, [10^3,0]) B - @fact maximum(map(abs, Images.imfilter_gaussian(imgcol, [10^3,10^3]) - mean(imgcol))) --> less_than(1e-4) - @fact maximum(map(abs, Images.imfilter_gaussian(imgcolf, [10^3,10^3]) - mean(imgcolf))) --> less_than(1e-4) - A = rand(4,5) - img = reinterpret(Images.Gray{Float64}, Images.grayim(A)) - imgf = Images.imfilter_gaussian(img, [2,2]) - @fact reinterpret(Float64, Images.data(imgf)) --> roughly(Images.imfilter_gaussian(A, [2,2])) - A = rand(3,4,5) - img = Images.colorim(A) - imgf = Images.imfilter_gaussian(img, [2,2]) - @fact reinterpret(Float64, Images.data(imgf)) --> roughly(Images.imfilter_gaussian(A, [0,2,2])) - - A = zeros(Int, 9, 9); A[5, 5] = 1 - @fact maximum(abs.(Images.imfilter_LoG(A, [1,1]) - Images.imlog(1.0))) --> less_than(EPS) - @fact maximum(Images.imfilter_LoG([0 0 0 0 1 0 0 0 0], [1,1]) - sum(Images.imlog(1.0),1)) --> less_than(EPS) - @fact maximum(Images.imfilter_LoG([0 0 0 0 1 0 0 0 0]', [1,1]) - sum(Images.imlog(1.0),2)) --> less_than(EPS) - - @fact Images.imaverage() --> fill(1/9, 3, 3) - @fact Images.imaverage([3,3]) --> fill(1/9, 3, 3) - @fact_throws ErrorException Images.imaverage([5]) + imgfft = fft(channelview(img), 2:3) + @fact Images.data(imgfft) --> roughly(fft(A, 2:3)) "test LlCbyo" + img2 = ifft(imgfft, 2:3) + @fact img2 --> roughly(A) "test D74cjO" end context("Features") do A = zeros(Int, 9, 9); A[5, 5] = 1 - @fact all(x->x true - @fact all(x->x true + @fact all(x->x true "test IIHdVc" + @fact all(x->x true "test CMOJKr" A = zeros(Int, 9, 9); A[[1:2;5],5]=1 - @fact findlocalmaxima(A) --> [(5,5)] - @fact findlocalmaxima(A,2) --> [(1,5),(2,5),(5,5)] - @fact findlocalmaxima(A,2,false) --> [(2,5),(5,5)] + @fact findlocalmaxima(A) --> [(5,5)] "test xyncRi" + @fact findlocalmaxima(A,2) --> [(1,5),(2,5),(5,5)] "test vRMsHj" + @fact findlocalmaxima(A,2,false) --> [(2,5),(5,5)] "test NpwSel" A = zeros(Int, 9, 9, 9); A[[1:2;5],5,5]=1 - @fact findlocalmaxima(A) --> [(5,5,5)] - @fact findlocalmaxima(A,2) --> [(1,5,5),(2,5,5),(5,5,5)] - @fact findlocalmaxima(A,2,false) --> [(2,5,5),(5,5,5)] + @fact findlocalmaxima(A) --> [(5,5,5)] "test lSv2tA" + @fact findlocalmaxima(A,2) --> [(1,5,5),(2,5,5),(5,5,5)] "test 4jrt8N" + @fact findlocalmaxima(A,2,false) --> [(2,5,5),(5,5,5)] "test 2awiPo" end context("Restriction") do @@ -406,16 +238,14 @@ facts("Algorithms") do [15.96875 24.625 20.96875; 32.875 50.5 42.875; 16.90625 25.875 21.90625]) - @fact B --> roughly(Btarget) + @fact B --> roughly(Btarget) "test g0lXjp" Argb = reinterpret(RGB, reinterpret(UFixed16, permutedims(A, (3,1,2)))) B = Images.restrict(Argb) - Tr = eltype(eltype(B)) - Bf = permutedims(reinterpret(Tr, B), (2,3,1)) - @fact Bf --> roughly(Btarget/reinterpret(one(UFixed16)), eps(Tr)^(2/3)) + Bf = permutedims(reinterpret(Float64, B), (2,3,1)) + @fact Bf --> roughly(Btarget/reinterpret(one(UFixed16)), 1e-12) "test IVByaq" Argba = reinterpret(RGBA{UFixed16}, reinterpret(UFixed16, A)) B = Images.restrict(Argba) - Tr = eltype(eltype(B)) - @fact reinterpret(Tr, B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(UFixed16)), eps(Tr)^(2/3)) + @fact reinterpret(Float64, B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(UFixed16)), 1e-12) "test z8K24e" A = reshape(1:60, 5, 4, 3) B = Images.restrict(A, (1,2,3)) @fact cat(3, [ 2.6015625 8.71875 6.1171875; @@ -423,15 +253,17 @@ facts("Algorithms") do 3.5390625 10.59375 7.0546875], [10.1015625 23.71875 13.6171875; 14.09375 32.875 18.78125; - 11.0390625 25.59375 14.5546875]) --> roughly(B) - imgcol["pixelspacing"] = [1,1] - imgr = Images.restrict(imgcol, (1,2)) - @fact pixelspacing(imgr) --> [2,2] - @fact pixelspacing(imgcol) --> [1,1] # issue #347 + 11.0390625 25.59375 14.5546875]) --> roughly(B) "test ClRQpv" + imgcolax = AxisArray(imgcol, :y, :x) + imgr = Images.restrict(imgcolax, (1,2)) + @fact pixelspacing(imgr) --> (2,2) "test tu0DXK" + @fact pixelspacing(imgcolax) --> (1,1) # issue #347 "test JR7awG" + @inferred(restrict(imgcolax, Axis{:y})) + @inferred(restrict(imgcolax, Axis{:x})) # Issue #395 img1 = colorim(fill(0.9, 3, 5, 5)) img2 = colorim(fill(U8(0.9), 3, 5, 5)) - @fact separate(restrict(img1)) --> roughly(separate(restrict(img2)), 0.01) + @fact separate(restrict(img1)) --> roughly(separate(restrict(img2)), 0.01) "test TH8OoL" end context("Erode/ dilate") do @@ -439,8 +271,8 @@ facts("Algorithms") do A[2,2,1] = 0.8 A[4,4,2] = 0.6 Ae = Images.erode(A) - @fact Ae --> zeros(size(A)) - Ad = Images.dilate(A) + @fact Ae --> zeros(size(A)) "test kT2gnC" + Ad = Images.dilate(A, 1:2) Ar = [0.8 0.8 0.8 0; 0.8 0.8 0.8 0; 0.8 0.8 0.8 0; @@ -449,8 +281,8 @@ facts("Algorithms") do 0 0 0 0; 0 0 0.6 0.6; 0 0 0.6 0.6] - @fact Ad --> cat(3, Ar, Ag, zeros(4,4)) - Ae = Images.erode(Ad) + @fact Ad --> cat(3, Ar, Ag, zeros(4,4)) "test AnS1W2" + Ae = Images.erode(Ad, 1:2) Ar = [0.8 0.8 0 0; 0.8 0.8 0 0; 0 0 0 0; @@ -459,9 +291,9 @@ facts("Algorithms") do 0 0 0 0; 0 0 0 0; 0 0 0 0.6] - @fact Ae --> cat(3, Ar, Ag, zeros(4,4)) + @fact Ae --> cat(3, Ar, Ag, zeros(4,4)) "test vs0TRg" # issue #311 - @fact Images.dilate(trues(3)) --> trues(3) + @fact Images.dilate(trues(3)) --> trues(3) "test Eykrqy" end context("Extrema_filter") do @@ -471,14 +303,14 @@ facts("Algorithms") do A[4,4] = 0.6 minval, maxval = extrema_filter(A, 2) matching = [vec(A[2:end]) .!= vec(maxval); false] - @fact A[reshape(matching, size(A))] --> [0.8, 0.6] + @fact A[reshape(matching, size(A))] --> [0.8, 0.6] "test nQ22cy" # 3d case A = zeros(5,5,5) A[2,2,2] = 0.7 A[4,4,3] = 0.5 minval, maxval = extrema_filter(A, 2) matching = [vec(A[2:end]) .!= vec(maxval); false] - @fact A[reshape(matching, size(A))] --> [0.7, 0.5] + @fact A[reshape(matching, size(A))] --> [0.7, 0.5] "test NXCGwQ" # 4d case A = zeros(5,5,5,5) A[2,2,2,2] = 0.7 @@ -486,12 +318,12 @@ facts("Algorithms") do A[3,4,3,2] = 0.5 minval, maxval = extrema_filter(A, 2) matching = [vec(A[2:end]) .!= vec(maxval); false] - @fact A[reshape(matching, size(A))] --> [0.4,0.7,0.5] + @fact A[reshape(matching, size(A))] --> [0.4,0.7,0.5] "test Cw5Mbc" x, y, z, t = ind2sub(size(A), find(A .== 0.4)) - @fact x[1] --> 4 - @fact y[1] --> 4 - @fact z[1] --> 3 - @fact t[1] --> 1 + @fact x[1] --> 4 "test ptjTjw" + @fact y[1] --> 4 "test KKwmrK" + @fact z[1] --> 3 "test Pz5dnk" + @fact t[1] --> 1 "test GFMLRG" # 2d case A = rand(5,5)/10 A[2,2] = 0.8 @@ -499,7 +331,7 @@ facts("Algorithms") do minval, maxval = extrema_filter(A, [2, 2]) matching = falses(A) matching[2:end, 2:end] = maxval .== A[2:end, 2:end] - @fact sort(A[matching])[end-1:end] --> [0.6, 0.8] + @fact sort(A[matching])[end-1:end] --> [0.6, 0.8] "test 1SqoTD" # 3d case A = rand(5,5,5)/10 A[2,2,2] = 0.7 @@ -508,7 +340,7 @@ facts("Algorithms") do minval, maxval = extrema_filter(A, [2, 2, 2]) matching = falses(A) matching[2:end, 2:end, 2:end] = maxval .== A[2:end, 2:end, 2:end] - @fact sort(A[matching])[end-2:end] --> [0.4, 0.5, 0.7] + @fact sort(A[matching])[end-2:end] --> [0.4, 0.5, 0.7] "test XJKNe1" # 4d case A = rand(5,5,5,5)/10 A[2,2,2,2] = 0.7 @@ -517,7 +349,7 @@ facts("Algorithms") do minval, maxval = extrema_filter(A, [2, 2, 2, 2]) matching = falses(A) matching[2:end, 2:end, 2:end, 2:end] = maxval .== A[2:end, 2:end, 2:end, 2:end] - @fact sort(A[matching])[end-2:end] --> [0.4, 0.5, 0.7] + @fact sort(A[matching])[end-2:end] --> [0.4, 0.5, 0.7] "test TqtAKq" end context("Opening / closing") do @@ -525,13 +357,13 @@ facts("Algorithms") do A[2,2,1] = 0.8 A[4,4,2] = 0.6 Ao = Images.opening(A) - @fact Ao --> zeros(size(A)) + @fact Ao --> zeros(size(A)) "test dRBjeP" A = zeros(10,10) A[4:7,4:7] = 1 B = copy(A) A[5,5] = 0 Ac = Images.closing(A) - @fact Ac --> B + @fact Ac --> B "test NFEnY3" end context("Morphological Top-hat") do @@ -540,9 +372,9 @@ facts("Algorithms") do Ae = copy(A) A[5:9, 5:9] = 1 Ao = Images.tophat(A) - @fact Ao --> Ae + @fact Ao --> Ae "test VxhCpF" Aoo = Images.tophat(Ae) - @fact Aoo --> Ae + @fact Aoo --> Ae "test xA9B1t" end context("Morphological Bottom-hat") do @@ -551,7 +383,7 @@ facts("Algorithms") do Ae = 1 - copy(A) A[5:9, 5:9] = 0 Ao = Images.bothat(A) - @fact Ao --> Ae + @fact Ao --> Ae "test olSLjv" end context("Morphological Gradient") do @@ -561,9 +393,9 @@ facts("Algorithms") do Ae = zeros(13, 13) Ae[4:10, 4:10] = 1 Ae[6:8, 6:8] = 0 - @fact Ao --> Ae + @fact Ao --> Ae "test FjldLK" Aee = Images.dilate(A) - Images.erode(A) - @fact Aee --> Ae + @fact Aee --> Ae "test PMRzxP" end context("Morphological Laplacian") do @@ -574,9 +406,9 @@ facts("Algorithms") do Ae[4:10, 4:10] = 1 Ae[5:9, 5:9] = -1 Ae[6:8, 6:8] = 0 - @fact Ao --> Ae + @fact Ao --> Ae "test 52XRJX" Aee = Images.dilate(A) + Images.erode(A) - 2A - @fact Aee --> Ae + @fact Aee --> Ae "test J1QcHc" end context("Label components") do @@ -586,21 +418,21 @@ facts("Algorithms") do 1 0 2 2] lbltarget1 = [1 2 0 4; 1 0 3 4] - @fact Images.label_components(A) --> lbltarget - @fact Images.label_components(A, [1]) --> lbltarget1 + @fact Images.label_components(A) --> lbltarget "test JroBn0" + @fact Images.label_components(A, [1]) --> lbltarget1 "test QJVu6m" connectivity = [false true false; true false true; false true false] - @fact Images.label_components(A, connectivity) --> lbltarget + @fact Images.label_components(A, connectivity) --> lbltarget "test vz5uon" connectivity = trues(3,3) lbltarget2 = [1 1 0 1; 1 0 1 1] - @fact Images.label_components(A, connectivity) --> lbltarget2 - @fact component_boxes(lbltarget) --> Vector{Tuple}[[(1,2),(2,3)],[(1,1),(2,2)],[(1,3),(2,4)]] - @fact component_lengths(lbltarget) --> [2,3,3] - @fact component_indices(lbltarget) --> Array{Int64}[[4,5],[1,2,3],[6,7,8]] - @fact component_subscripts(lbltarget) --> Array{Tuple}[[(2,2),(1,3)],[(1,1),(2,1),(1,2)],[(2,3),(1,4),(2,4)]] - @fact component_centroids(lbltarget) --> Tuple[(1.5,2.5),(4/3,4/3),(5/3,11/3)] + @fact Images.label_components(A, connectivity) --> lbltarget2 "test CFEvKh" + @fact component_boxes(lbltarget) --> Vector{Tuple}[[(1,2),(2,3)],[(1,1),(2,2)],[(1,3),(2,4)]] "test X6qLkS" + @fact component_lengths(lbltarget) --> [2,3,3] "test BoBrn8" + @fact component_indices(lbltarget) --> Array{Int64}[[4,5],[1,2,3],[6,7,8]] "test iHJcSR" + @fact component_subscripts(lbltarget) --> Array{Tuple}[[(2,2),(1,3)],[(1,1),(2,1),(1,2)],[(2,3),(1,4),(2,4)]] "test gyzoNi" + @fact component_centroids(lbltarget) --> Tuple[(1.5,2.5),(4/3,4/3),(5/3,11/3)] "test RMvhLP" end context("Phantoms") do @@ -613,7 +445,7 @@ facts("Algorithms") do 0.0 0.0 1.0 0.2 0.2 1.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] Q = Images.shepp_logan(8) - @fact norm((P-Q)[:]) --> less_than(1e-10) + @fact norm((P-Q)[:]) --> less_than(1e-10) "test Xl6BcQ" P = [ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 2.0 1.02 1.02 2.0 0.0 0.0; 0.0 0.0 1.02 1.03 1.03 1.02 0.0 0.0; @@ -623,44 +455,44 @@ facts("Algorithms") do 0.0 0.0 2.0 1.02 1.02 2.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] Q = Images.shepp_logan(8, highContrast=false) - @fact norm((P-Q)[:]) --> less_than(1e-10) + @fact norm((P-Q)[:]) --> less_than(1e-10) "test MFwM6o" end context("Image resize") do img = convert(Images.Image, zeros(10,10)) img2 = Images.imresize(img, (5,5)) - @fact length(img2) --> 25 + @fact length(img2) --> 25 "test l5xQhW" end context("Interpolations") do img = zeros(Float64, 5, 5) - @fact bilinear_interpolation(img, 4.5, 5.5) --> 0.0 - @fact bilinear_interpolation(img, 4.5, 3.5) --> 0.0 + @fact bilinear_interpolation(img, 4.5, 5.5) --> 0.0 "test bhkr9P" + @fact bilinear_interpolation(img, 4.5, 3.5) --> 0.0 "test In1KLU" for i in [1.0, 2.0, 5.0, 7.0, 9.0] img = ones(Float64, 5, 5) * i - @fact (bilinear_interpolation(img, 3.5, 4.5) == i) --> true - @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # X_MAX == X_MIN - @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # Y_MAX == Y_MIN - @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # BOTH EQUAL - @fact (bilinear_interpolation(img, 2.8, 1.9) == i) --> true + @fact (bilinear_interpolation(img, 3.5, 4.5) == i) --> true "test YiwKrn" + @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # X_MAX == X_MIN "test zzKRPq" + @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # Y_MAX == Y_MIN "test RbjV3L" + @fact (bilinear_interpolation(img, 3.2, 4) == i) --> true # BOTH EQUAL "test WVPgJQ" + @fact (bilinear_interpolation(img, 2.8, 1.9) == i) --> true "test S4fl6z" # One dim out of bounds - @fact isapprox(bilinear_interpolation(img, 0.5, 1.5), 0.5 * i) --> true - @fact isapprox(bilinear_interpolation(img, 0.5, 1.6), 0.5 * i) --> true - @fact isapprox(bilinear_interpolation(img, 0.5, 1.8), 0.5 * i) --> true + @fact isapprox(bilinear_interpolation(img, 0.5, 1.5), 0.5 * i) --> true "test 5vocwI" + @fact isapprox(bilinear_interpolation(img, 0.5, 1.6), 0.5 * i) --> true "test ssXoVz" + @fact isapprox(bilinear_interpolation(img, 0.5, 1.8), 0.5 * i) --> true "test Mg0DVi" # Both out of bounds (corner) - @fact isapprox(bilinear_interpolation(img, 0.5, 0.5), 0.25 * i) --> true + @fact isapprox(bilinear_interpolation(img, 0.5, 0.5), 0.25 * i) --> true "test CtbuP5" end img = reshape(1.0:1.0:25.0, 5, 5) - @fact bilinear_interpolation(img, 1.5, 2) --> 6.5 - @fact bilinear_interpolation(img, 2, 1.5) --> 4.5 - @fact bilinear_interpolation(img, 2, 1) --> 2.0 - @fact bilinear_interpolation(img, 1.5, 2.5) --> 9.0 - @fact bilinear_interpolation(img, 1.5, 3.5) --> 14.0 - @fact bilinear_interpolation(img, 1.5, 4.5) --> 19.0 - @fact bilinear_interpolation(img, 1.5, 5.5) --> 10.75 + @fact bilinear_interpolation(img, 1.5, 2) --> 6.5 "test J81zIL" + @fact bilinear_interpolation(img, 2, 1.5) --> 4.5 "test fqXV9r" + @fact bilinear_interpolation(img, 2, 1) --> 2.0 "test 4T1SSb" + @fact bilinear_interpolation(img, 1.5, 2.5) --> 9.0 "test mBjvW9" + @fact bilinear_interpolation(img, 1.5, 3.5) --> 14.0 "test EHqnCK" + @fact bilinear_interpolation(img, 1.5, 4.5) --> 19.0 "test FGWq37" + @fact bilinear_interpolation(img, 1.5, 5.5) --> 10.75 "test z2ejkZ" end From 0873754b847e421b5e973a8c258c264c35ff9e98 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 20 Sep 2016 11:24:06 -0500 Subject: [PATCH 07/38] Get edge tests working --- NEWS.md | 14 +- src/deprecated.jl | 6 + src/edge.jl | 147 +++++++++-------- test/old/edge.jl | 395 ++++++++++++++++------------------------------ 4 files changed, 241 insertions(+), 321 deletions(-) diff --git a/NEWS.md b/NEWS.md index 24da5942..4529e946 100644 --- a/NEWS.md +++ b/NEWS.md @@ -74,11 +74,21 @@ Key changes (of which many are breaking): Other changes: -- `extrema_filter` is being deprecated in favor of +- The gradient components returned by `imgradients` match the + dimensions of the input; in `g1, g2, ... = imgradients(img, + ...)`, `g1` corresponds to the gradient along the first dimension, + `g2` along the second, and so on. + +- `sobel` and other filters have been normalized so that the returned + "gradient components" are scaled to estimate the actual + derivatives. For example, for `sobel` the normalization factor is + 1/8 compared to earlier releases. + +- `extrema_filter` has been deprecated in favor of `rankfilter(extrema, A, window)`. However, this returns an array of `(min,max)` tuples rather than separate `min`, `max` arrays. This is intended to transition towards a future API where one can pass `min` - or `max` in place of `extrema` to obtain just get one of + or `max` in place of `extrema` to obtain just one of these. Currently, you can retrieve the `min` array with `first.(mm)` and the `max` array with `last.(mm)`. diff --git a/src/deprecated.jl b/src/deprecated.jl index 2cbaf05a..a5bcf878 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -32,3 +32,9 @@ function restrict{S<:String}(img::AbstractArray, region::Union{Tuple{String,Vara end restrict(img, regioni) end + +function magnitude_phase(img::AbstractArray, method::AbstractString, border::AbstractString="replicate") + f = ImageFiltering.kernelfunc_lookup(method) + depwarn("magnitude_phase(img, method::AbstractString, [border]) is deprecated, use magnitude_phase(img, $f, [border]) instead", :magnitude_phase) + magnitude_phase(img, f, border) +end diff --git a/src/edge.jl b/src/edge.jl index 38b53867..b3b20466 100644 --- a/src/edge.jl +++ b/src/edge.jl @@ -1,89 +1,94 @@ ### Edge and Gradient Related Image Operations ### -# Magnitude of gradient, calculated from X and Y image gradients -""" -``` -m = magnitude(grad_x, grad_y) -``` +typealias GrayLike Union{Number,AbstractGray} -Calculates the magnitude of the gradient images given by `grad_x` and `grad_y`. -Equivalent to ``sqrt(grad_x.^2 + grad_y.^2)``. -Returns a magnitude image the same size as `grad_x` and `grad_y`. +# Phase (angle of steepest gradient ascent), calculated from X and Y gradient images """ -magnitude(grad_x::AbstractArray, grad_y::AbstractArray) = - @compat hypot.(grad_x, grad_y) -magnitude(img1::AbstractImageDirect, img2::AbstractImageDirect) = - hypot(img1, img2) + phase(grad_x, grad_y) -> p -# Phase (angle of steepest gradient ascent), calculated from X and Y gradient images +Calculate the rotation angle of the gradient given by `grad_x` and +`grad_y`. Equivalent to `atan2(-grad_y, grad_x)`, except that when both `grad_x` and +`grad_y` are effectively zero, the corresponding angle is set to zero. """ -``` -p = phase(grad_x, grad_y) -``` +function phase{T<:Number}(grad_x::T, grad_y::T, tol=sqrt(eps(T))) + atan2(-grad_y, grad_x) * ((abs(grad_x) > tol) | (abs(grad_y) > tol)) +end +phase(grad_x::Number, grad_y::Number) = phase(promote(grad_x, grad_y)...) +phase(grad_x::GrayLike, grad_y::GrayLike) = phase(gray(grad_x), gray(grad_y)) -Calculates the rotation angle of the gradient images given by `grad_x` and -`grad_y`. Equivalent to ``atan2(-grad_y, grad_x)``. When both ``grad_x[i]`` and -``grad_y[i]`` are zero, the corresponding angle is set to zero. +phase(grad_x::AbstractRGB, grad_y::AbstractRGB) = phase(vecsum(grad_x), vecsum(grad_y)) -Returns a phase image the same size as `grad_x` and `grad_y`, with values in [-pi,pi]. -""" -function phase{T}(grad_x::AbstractArray{T}, grad_y::AbstractArray{T}) - EPS = sqrt(eps(eltype(T))) - # Set phase to zero when both gradients are close to zero - reshape([atan2(-grad_y[i], grad_x[i]) * ((abs(grad_x[i]) > EPS) | (abs(grad_y[i]) > EPS)) - for i=1:length(grad_x)], size(grad_x)) +magnitude_phase(grad_x::GrayLike, grad_y::GrayLike) = + hypot(grad_x, grad_y), phase(grad_x, grad_y) + +function magnitude_phase(grad_x::AbstractRGB, grad_y::AbstractRGB) + gx, gy = vecsum(grad_x), vecsum(grad_y) + magnitude_phase(gx, gy) end -function phase(grad_x::AbstractImageDirect, grad_y::AbstractImageDirect) - img = copyproperties(grad_x, phase(data(grad_x), data(grad_y))) - img["limits"] = (-float(pi),float(pi)) - img +vecsum(c::AbstractRGB) = float(red(c)) + float(green(c)) + float(blue(c)) + +## TODO? orientation seems nearly redundant with phase, deprecate? + +""" + orientation(grad_x, grad_y) -> orient + +Calculate the orientation angle of the strongest edge from gradient images +given by `grad_x` and `grad_y`. Equivalent to `atan2(grad_x, grad_y)`. When +both `grad_x` and `grad_y` are effectively zero, the corresponding angle is set to +zero. +""" +function orientation{T<:Number}(grad_x::T, grad_y::T, tol=sqrt(eps(T))) + atan2(grad_x, grad_y) * ((abs(grad_x) > tol) | (abs(grad_y) > tol)) end +orientation(grad_x::Number, grad_y::Number) = orientation(promote(grad_x, grad_y)...) +orientation(grad_x::GrayLike, grad_y::GrayLike) = orientation(gray(grad_x), gray(grad_y)) -# Orientation of the strongest edge at a point, calculated from X and Y gradient images -# Note that this is perpendicular to the phase at that point, except where -# both gradients are close to zero. +orientation(grad_x::AbstractRGB, grad_y::AbstractRGB) = orientation(vecsum(grad_x), vecsum(grad_y)) +# Magnitude of gradient, calculated from X and Y image gradients """ ``` -orient = orientation(grad_x, grad_y) +m = magnitude(grad_x, grad_y) ``` -Calculates the orientation angle of the strongest edge from gradient images -given by `grad_x` and `grad_y`. Equivalent to ``atan2(grad_x, grad_y)``. When -both `grad_x[i]` and `grad_y[i]` are zero, the corresponding angle is set to -zero. +Calculates the magnitude of the gradient images given by `grad_x` and `grad_y`. +Equivalent to ``sqrt(grad_x.^2 + grad_y.^2)``. -Returns a phase image the same size as `grad_x` and `grad_y`, with values in -[-pi,pi]. +Returns a magnitude image the same size as `grad_x` and `grad_y`. """ -function orientation{T}(grad_x::AbstractArray{T}, grad_y::AbstractArray{T}) - EPS = sqrt(eps(eltype(T))) - # Set orientation to zero when both gradients are close to zero - # (grad_y[i] should probably be negated here, but isn't for consistency with earlier releases) - reshape([atan2(grad_x[i], grad_y[i]) * ((abs(grad_x[i]) > EPS) | (abs(grad_y[i]) > EPS)) - for i=1:length(grad_x)], size(grad_x)) -end +magnitude(grad_x::AbstractArray, grad_y::AbstractArray) = hypot.(grad_x, grad_y) -function orientation(grad_x::AbstractImageDirect, grad_y::AbstractImageDirect) - img = copyproperties(grad_x, orientation(data(grad_x), data(grad_y))) - img["limits"] = (-float(pi),float(pi)) - img -end +Base.hypot(x::AbstractRGB, y::AbstractRGB) = hypot(vecsum(x), vecsum(y)) + +phase(grad_x::AbstractArray, grad_y::AbstractArray) = phase.(grad_x, grad_y) + +# Orientation of the strongest edge at a point, calculated from X and Y gradient images +# Note that this is perpendicular to the phase at that point, except where +# both gradients are close to zero. + +orientation{T}(grad_x::AbstractArray{T}, grad_y::AbstractArray{T}) = orientation.(grad_x, grad_y) # Return both the magnitude and phase in one call """ -`m, p = magnitude_phase(grad_x, grad_y)` + magnitude_phase(grad_x, grad_y) -> m, p Convenience function for calculating the magnitude and phase of the gradient images given in `grad_x` and `grad_y`. Returns a tuple containing the magnitude and phase images. See `magnitude` and `phase` for details. """ -magnitude_phase(grad_x::AbstractArray, grad_y::AbstractArray) = (magnitude(grad_x,grad_y), phase(grad_x,grad_y)) +function magnitude_phase{T}(grad_x::AbstractArray{T}, grad_y::AbstractArray{T}) + m = similar(grad_x, eltype(T)) + p = similar(m) + for I in eachindex(grad_x, grad_y) + m[I], p[I] = magnitude_phase(grad_x[I], grad_y[I]) + end + m, p +end -# Return the magnituded and phase of the gradients in an image -function magnitude_phase(img::AbstractArray, method::AbstractString="ando3", border::AbstractString="replicate") +# Return the magnitude and phase of the gradients in an image +function magnitude_phase(img::AbstractArray, method::Function=KernelFactors.ando3, border::AbstractString="replicate") grad_x, grad_y = imgradients(img, method, border) return magnitude_phase(grad_x, grad_y) end @@ -401,21 +406,22 @@ Parameters : as quantiles or absolute values """ -function canny{T}(img::AbstractArray{T, 2}, sigma::Number = 1.4, upperThreshold::Number = 0.90, lowerThreshold::Number = 0.10; percentile::Bool = true) - img_gray = convert(Image{Images.Gray{U8}}, img) +function canny{T<:Union{Number,Gray}}(img_gray::AbstractMatrix{T}, sigma::Number = 1.4, upperThreshold::Number = 0.90, lowerThreshold::Number = 0.10; percentile::Bool = true) img_grayf = imfilter_gaussian(img_gray, [sigma,sigma]) - img_grad_x, img_grad_y = imgradients(img_grayf, "sobel") + img_grad_y, img_grad_x = imgradients(img_grayf, KernelFactors.sobel) img_mag, img_phase = magnitude_phase(img_grad_x, img_grad_y) img_nonMaxSup = thin_edges_nonmaxsup(img_mag, img_phase) - if percentile == true + if percentile upperThreshold = StatsBase.percentile(img_nonMaxSup[:], upperThreshold * 100) lowerThreshold = StatsBase.percentile(img_nonMaxSup[:], lowerThreshold * 100) end img_thresholded = hysteresis_thresholding(img_nonMaxSup, upperThreshold, lowerThreshold) - edges = map(i -> i < 0.9 ? zero(Gray{U8}) : one(Gray{U8}), img_thresholded) + S = eltype(img_thresholded) + edges = map(i -> i < 0.9 ? zero(S) : one(S), img_thresholded) edges end +canny(img::AbstractMatrix, args...) = canny(convert(Array{Gray}, img), args...) function hysteresis_thresholding{T}(img_nonMaxSup::AbstractArray{T, 2}, upperThreshold::Number, lowerThreshold::Number) img_thresholded = map(i -> i > lowerThreshold ? i > upperThreshold ? 1.0 : 0.5 : 0.0, img_nonMaxSup) @@ -440,4 +446,21 @@ function hysteresis_thresholding{T}(img_nonMaxSup::AbstractArray{T, 2}, upperThr end img_thresholded end -0 + +function padindexes{T,n}(img::AbstractArray{T,n}, dim, prepad, postpad, border::AbstractString) + M = size(img, dim) + I = Array(Int, M + prepad + postpad) + I = [(1 - prepad):(M + postpad);] + @compat if border == "replicate" + I = min.(max.(I, 1), M) + elseif border == "circular" + I = 1 .+ mod.(I .- 1, M) + elseif border == "symmetric" + I = [1:M; M:-1:1][1 .+ mod.(I .- 1, 2 * M)] + elseif border == "reflect" + I = [1:M; M-1:-1:2][1 .+ mod.(I .- 1, 2 * M - 2)] + else + error("unknown border condition") + end + I +end diff --git a/test/old/edge.jl b/test/old/edge.jl index a344659b..8f68b3b5 100644 --- a/test/old/edge.jl +++ b/test/old/edge.jl @@ -1,5 +1,4 @@ -module TestImagesEdge -using Images, FactCheck, Base.Test, Colors, Compat, Graphics +using Images, FactCheck, Base.Test, Colors, Compat global checkboard @@ -38,38 +37,37 @@ facts("Edge") do #General Checks img = zeros(10, 10) edges = canny(img) - @fact eltype(edges.data) --> Gray{U8} - @fact all(edges .== 0.0) --> true + @fact all(edges .== 0.0) --> true "test XPvVle" #Box Edges img[2:end-1, 2:end-1] = 1 edges = canny(img) - @fact all(edges[2:end-1, 2] .== 1.0) --> true - @fact all(edges[2:end-1, end-1] .== 1.0) --> true - @fact all(edges[2, 2:end-1] .== 1.0) --> true - @fact all(edges[end-1, 2:end-1] .== 1.0) --> true - @fact all(edges[3:end-2, 3:end-2] .== 0.0) --> true - - edges = canny(img, 1.4, 0.9, 0.2, percentile = false) - @fact all(edges[2:end-1, 2] .== 1.0) --> true - @fact all(edges[2:end-1, end-1] .== 1.0) --> true - @fact all(edges[2, 2:end-1] .== 1.0) --> true - @fact all(edges[end-1, 2:end-1] .== 1.0) --> true - @fact all(edges[3:end-2, 3:end-2] .== 0.0) --> true + @fact all(edges[2:end-1, 2] .== 1.0) --> true "test XaR3zT" + @fact all(edges[2:end-1, end-1] .== 1.0) --> true "test hYEggy" + @fact all(edges[2, 2:end-1] .== 1.0) --> true "test SGpiuC" + @fact all(edges[end-1, 2:end-1] .== 1.0) --> true "test O1jzIi" + @fact all(edges[3:end-2, 3:end-2] .== 0.0) --> true "test mnlN9g" + + edges = canny(img, 1.4, 0.9/8, 0.2/8, percentile = false) + @fact all(edges[2:end-1, 2] .== 1.0) --> true "test 8vqfWg" + @fact all(edges[2:end-1, end-1] .== 1.0) --> true "test shxyUG" + @fact all(edges[2, 2:end-1] .== 1.0) --> true "test HcRl6t" + @fact all(edges[end-1, 2:end-1] .== 1.0) --> true "test gdzLcr" + @fact all(edges[3:end-2, 3:end-2] .== 0.0) --> true "test U3xnBr" #Checkerboard - Corners are not detected as Edges! img = checkerboard(Gray, 5, 3) edges = canny(img, 1.4, 0.8, 0.2) id = [1,2,3,4,6,7,8,9,10,12,13,14,15] - @fact all(edges[id, id] .== zero(Gray)) --> true + @fact all(edges[id, id] .== zero(Gray)) --> true "test iXLmrb" id = [5, 11] id2 = [1,2,3,4,7,8,9,12,13,14,15] id3 = [5,6,10,11] - @fact all(edges[id, id2] .== one(Gray)) --> true - @fact all(edges[id2, id] .== one(Gray)) --> true - @fact all(edges[id, id3] .== zero(Gray)) --> true - @fact all(edges[id3, id] .== zero(Gray)) --> true + @fact all(edges[id, id2] .== one(Gray)) --> true "test sdlPvR" + @fact all(edges[id2, id] .== one(Gray)) --> true "test sMVseP" + @fact all(edges[id, id3] .== zero(Gray)) --> true "test jDMJux" + @fact all(edges[id3, id] .== zero(Gray)) --> true "test sAPU9M" #Diagonal Edge img = zeros(10,10) @@ -77,199 +75,149 @@ facts("Edge") do img[diagind(img, 1)] = 1 img[diagind(img, -1)] = 1 edges = canny(img) - @fact all(edges[diagind(edges, 2)] .== 1.0) --> true - @fact all(edges[diagind(edges, -2)] .== 1.0) --> true + @fact all(edges[diagind(edges, 2)] .== 1.0) --> true "test ZnFAXW" + @fact all(edges[diagind(edges, -2)] .== 1.0) --> true "test S0bdlI" nondiags = setdiff(1:1:100, union(diagind(edges, 2), diagind(edges, -2))) - @fact all(edges[nondiags] .== 0.0) --> true + @fact all(edges[nondiags] .== 0.0) --> true "test WDQfYZ" #Checks Hysteresis Thresholding img = ones(Gray{U8}, (10, 10)) img[3:7, 3:7] = 0.0 img[4:6, 4:6] = 0.7 thresholded = Images.hysteresis_thresholding(img, 0.9, 0.8) - @fact all(thresholded[3:7, 3:7] .== 0.0) --> true - @fact all(thresholded[1:2, :] .== 0.9) --> true - @fact all(thresholded[:, 1:2] .== 0.9) --> true - @fact all(thresholded[8:10, :] .== 0.9) --> true - @fact all(thresholded[:, 8:10] .== 0.9) --> true + @fact all(thresholded[3:7, 3:7] .== 0.0) --> true "test 3XLFf1" + @fact all(thresholded[1:2, :] .== 0.9) --> true "test x3Gdlv" + @fact all(thresholded[:, 1:2] .== 0.9) --> true "test 15gDt3" + @fact all(thresholded[8:10, :] .== 0.9) --> true "test YlZ9mL" + @fact all(thresholded[:, 8:10] .== 0.9) --> true "test a8kckT" thresholded = Images.hysteresis_thresholding(img, 0.9, 0.6) - @fact all(thresholded[4:6, 4:6] .== 0.5) --> true - @fact all(thresholded[3:7, 3:7] .< 0.9) --> true - @fact all(thresholded[1:2, :] .== 0.9) --> true - @fact all(thresholded[:, 1:2] .== 0.9) --> true - @fact all(thresholded[8:10, :] .== 0.9) --> true - @fact all(thresholded[:, 8:10] .== 0.9) --> true + @fact all(thresholded[4:6, 4:6] .== 0.5) --> true "test mvXBYG" + @fact all(thresholded[3:7, 3:7] .< 0.9) --> true "test C424vj" + @fact all(thresholded[1:2, :] .== 0.9) --> true "test Ll7Ks2" + @fact all(thresholded[:, 1:2] .== 0.9) --> true "test DL17A5" + @fact all(thresholded[8:10, :] .== 0.9) --> true "test p6iPF2" + @fact all(thresholded[:, 8:10] .== 0.9) --> true "test U1rPYX" img[3, 5] = 0.7 thresholded = Images.hysteresis_thresholding(img, 0.9, 0.6) - @fact all(thresholded[4:6, 4:6] .== 0.9) --> true - @fact all(thresholded[3:7, 3] .== 0.0) --> true - @fact all(thresholded[3:7, 7] .== 0.0) --> true - @fact all(thresholded[7, 3:7] .== 0.0) --> true - @fact all(vec(thresholded[3, 3:7]) .== [0.0, 0.0, 0.9, 0.0, 0.0]) --> true - @fact all(thresholded[1:2, :] .== 0.9) --> true - @fact all(thresholded[:, 1:2] .== 0.9) --> true - @fact all(thresholded[8:10, :] .== 0.9) --> true - @fact all(thresholded[:, 8:10] .== 0.9) --> true + @fact all(thresholded[4:6, 4:6] .== 0.9) --> true "test sAo0s9" + @fact all(thresholded[3:7, 3] .== 0.0) --> true "test FL5swa" + @fact all(thresholded[3:7, 7] .== 0.0) --> true "test H1IfJw" + @fact all(thresholded[7, 3:7] .== 0.0) --> true "test SC1yNK" + @fact all(vec(thresholded[3, 3:7]) .== [0.0, 0.0, 0.9, 0.0, 0.0]) --> true "test 0YsjuY" + @fact all(thresholded[1:2, :] .== 0.9) --> true "test 9nWiWC" + @fact all(thresholded[:, 1:2] .== 0.9) --> true "test VhcOoO" + @fact all(thresholded[8:10, :] .== 0.9) --> true "test qjmIqQ" + @fact all(thresholded[:, 8:10] .== 0.9) --> true "test MWZ3sx" end SZ=5 cb_array = checkerboard(SZ,3) - cb_image_xy = grayim(cb_array) - cb_image_yx = grayim(cb_array) - cb_image_yx["spatialorder"] = ["y","x"] - cb_image_gray = convert(Image{Gray}, cb_image_xy) - cb_image_rgb = convert(Image{RGB}, cb_image_xy) - cb_image_rgb2 = convert(Image{RGB{Float64}}, cb_image_xy) + cb_gray = grayim(cb_array) + cb_rgb = convert(Array{RGB}, cb_gray) + cb_rgbf64 = convert(Array{RGB{Float64}}, cb_gray) + # TODO: now that all of these behave nearly the same, put in a + # loop over the array type (only have to adjust tests for `red`) @compat context("Checkerboard") do - for method in ["sobel", "prewitt", "ando3", "ando4", "ando5", "ando4_sep", "ando5_sep"] + for method in ["sobel", "prewitt", "ando3", "ando4", "ando5"] ## Checkerboard array - (agx, agy) = imgradients(cb_array, method) + (agy, agx) = imgradients(cb_array, method) amag = magnitude(agx, agy) agphase = phase(agx, agy) - @fact (amag, agphase) --> magnitude_phase(agx, agy) + @fact (amag, agphase) --> magnitude_phase(agx, agy) "test 80oF4m" - @fact agx[1,SZ] --> less_than(0.0) # white to black transition - @fact agx[1,2*SZ] --> greater_than(0.0) # black to white transition - @fact agy[SZ,1] --> less_than(0.0) # white to black transition - @fact agy[2*SZ,1] --> greater_than(0.0) # black to white transition + @fact agx[1,SZ] --> less_than(0.0) "test 4N2OkG" # white to black transition + @fact agx[1,2*SZ] --> greater_than(0.0) "test QF4xEq" # black to white transition + @fact agy[SZ,1] --> less_than(0.0) "test Lcw3dQ" # white to black transition + @fact agy[2*SZ,1] --> greater_than(0.0) "test XIhUPC" # black to white transition # Test direction of increasing gradient - @fact cos(agphase[1,SZ]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(agphase[1,2*SZ]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(agphase[SZ,1]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(agphase[2*SZ,1]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) + @fact cos(agphase[1,SZ]) - (-1.0) --> less_than(EPS) "test MDZZJR" # increasing left (= pi radians) + @fact cos(agphase[1,2*SZ]) - 1.0 --> less_than(EPS) "test UI49Lu" # increasing right (= 0 radians) + @fact sin(agphase[SZ,1]) - 1.0 --> less_than(EPS) "test 8L7FiR" # increasing up (= pi/2 radians) + @fact sin(agphase[2*SZ,1]) - (-1.0) --> less_than(EPS) "test o0CIl6" # increasing down (= -pi/2 radians) # Test that orientation is perpendicular to gradient aorient = orientation(agx, agy) - @fact all((|).(cos.(agphase) .* cos.(aorient) .+ - sin.(agphase) .* sin.(aorient) .< EPS, - (&).(agphase .== 0.0, aorient .== 0.0))) --> true + @fact all((cos.(agphase) .* cos.(aorient) .+ + sin.(agphase) .* sin.(aorient) .< EPS) | + ((agphase .== 0.0) & (aorient .== 0.0))) --> true "test zPGMub" # this part is where both are zero because there is no gradient - ## Checkerboard Image with row major order + ## Checkerboard with Gray pixels - (gx, gy) = imgradients(cb_image_xy, method) - mag = magnitude(gx, gy) - gphase = phase(gx, gy) - @fact (mag, gphase) --> magnitude_phase(gx, gy) - - @fact gx[SZ,1] --> less_than(0.0) # white to black transition - @fact gx[2*SZ,1] --> greater_than(0.0) # black to white transition - @fact gy[1,SZ] --> less_than(0.0) # white to black transition - @fact gy[1,2*SZ] --> greater_than(0.0) # black to white transition - - @fact cos(gphase[SZ,1]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(gphase[2*SZ,1]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(gphase[1,SZ]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(gphase[1,2*SZ]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) - - # Test that orientation is perpendicular to gradient - orient = orientation(gx, gy) - @fact all((|).(cos.(gphase) .* cos.(orient) .+ - sin.(gphase) .* sin.(orient) .< EPS, - (&).(gphase .== 0.0, orient .== 0.0))) --> true - # this part is where both are zero because there is no gradient - - ## Checkerboard Image with column-major order - - (gx, gy) = imgradients(cb_image_yx, method) - mag = magnitude(gx, gy) - gphase = phase(gx, gy) - @fact (mag, gphase) --> magnitude_phase(gx, gy) - - @fact gx[1,SZ] --> less_than(0.0) # white to black transition - @fact gx[1,2*SZ] --> greater_than(0.0) # black to white transition - @fact gy[SZ,1] --> less_than(0.0) # white to black transition - @fact gy[2*SZ,1] --> greater_than(0.0) # black to white transition - - # Test direction of increasing gradient - @fact cos(gphase[1,SZ]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(gphase[1,2*SZ]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(gphase[SZ,1]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(gphase[2*SZ,1]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) - - # Test that orientation is perpendicular to gradient - orient = orientation(gx, gy) - @fact all((|).(cos.(gphase) .* cos.(orient) .+ - sin.(gphase) .* sin.(orient) .< EPS, - (&).(gphase .== 0.0, orient .== 0.0))) --> true - # this part is where both are zero because there is no gradient - - ## Checkerboard Image with Gray pixels - - (gxg, gyg) = imgradients(cb_image_gray, method) + (gyg, gxg) = imgradients(cb_gray, method) magg = magnitude(gxg, gyg) gphaseg = phase(gxg, gyg) - @fact (magg, gphaseg) --> magnitude_phase(gxg, gyg) + @fact (magg, gphaseg) --> magnitude_phase(gxg, gyg) "test AM0uph" - @fact gxg[SZ,1] --> less_than(0.0) # white to black transition - @fact gxg[2*SZ,1] --> greater_than(0.0) # black to white transition - @fact gyg[1,SZ] --> less_than(0.0) # white to black transition - @fact gyg[1,2*SZ] --> greater_than(0.0) # black to white transition + @fact gyg[SZ,1] --> less_than(0.0) "test cQKPsb" # white to black transition + @fact gyg[2*SZ,1] --> greater_than(0.0) "test 5wgkII" # black to white transition + @fact gxg[1,SZ] --> less_than(0.0) "test EeAjQ1" # white to black transition + @fact gxg[1,2*SZ] --> greater_than(0.0) "test dua7cD" # black to white transition - @fact cos(gphaseg[SZ,1]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(gphaseg[2*SZ,1]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(gphaseg[1,SZ]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(gphaseg[1,2*SZ]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) + @fact cos(gphaseg[1,SZ]) - (-1.0) --> less_than(EPS) "test TQ0aJN" # increasing left (= pi radians) + @fact cos(gphaseg[1,2*SZ]) - 1.0 --> less_than(EPS) "test Mf2h17" # increasing right (= 0 radians) + @fact sin(gphaseg[SZ,1]) - 1.0 --> less_than(EPS) "test fKv3Vl" # increasing up (= pi/2 radians) + @fact sin(gphaseg[2*SZ,1]) - (-1.0) --> less_than(EPS) "test CZQeGF" # increasing down (= -pi/2 radians) # Test that orientation is perpendicular to gradient orientg = orientation(gxg, gyg) - @fact all((|).(cos.(gphaseg) .* cos.(orientg) .+ - sin.(gphaseg) .* sin.(orientg) .< EPS, - (&).(gphaseg .== 0.0, orientg .== 0.0))) --> true + @fact all((cos.(gphaseg) .* cos.(orientg) .+ + sin.(gphaseg) .* sin.(orientg) .< EPS) | + ((gphaseg .== 0.0) & (orientg .== 0.0))) --> true "test BsoAOy" # this part is where both are zero because there is no gradient - ## Checkerboard Image with RBG pixels + ## Checkerboard with RBG pixels - (gx_rgb, gy_rgb) = imgradients(cb_image_rgb, method) + (gy_rgb, gx_rgb) = imgradients(cb_rgb, method) mag_rgb = magnitude(gx_rgb, gy_rgb) gphase_rgb = phase(gx_rgb, gy_rgb) - @fact (mag_rgb, gphase_rgb) --> magnitude_phase(gx_rgb, gy_rgb) + @fact (mag_rgb, gphase_rgb) --> magnitude_phase(gx_rgb, gy_rgb) "test YfK3X2" - @fact gx_rgb[1,SZ,1] --> less_than(0.0) # white to black transition - @fact gx_rgb[1,2*SZ,1] --> greater_than(0.0) # black to white transition - @fact gy_rgb[1,1,SZ] --> less_than(0.0) # white to black transition - @fact gy_rgb[1,1,2*SZ] --> greater_than(0.0) # black to white transition + @fact red(gy_rgb[SZ,1]) --> less_than(0.0) "test 1WYzkc" # white to black transition + @fact red(gy_rgb[2*SZ,1]) --> greater_than(0.0) "test rZ10Ar" # black to white transition + @fact red(gx_rgb[1,SZ]) --> less_than(0.0) "test aoRWZJ" # white to black transition + @fact red(gx_rgb[1,2*SZ]) --> greater_than(0.0) "test kNDrlj" # black to white transition - @fact cos(gphase_rgb[1,SZ,1]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(gphase_rgb[1,2*SZ,1]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(gphase_rgb[1,1,SZ]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(gphase_rgb[1,1,2*SZ]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) + @fact cos(gphase_rgb[1,SZ]) - (-1.0) --> less_than(EPS) "test qcAgh5" # increasing left (= pi radians) + @fact cos(gphase_rgb[1,2*SZ]) - 1.0 --> less_than(EPS) "test Mb9flX" # increasing right (= 0 radians) + @fact sin(gphase_rgb[SZ,1]) - 1.0 --> less_than(EPS) "test Wxzwl1" # increasing up (= pi/2 radians) + @fact sin(gphase_rgb[2*SZ,1]) - (-1.0) --> less_than(EPS) "test iCilcm" # increasing down (= -pi/2 radians) # Test that orientation is perpendicular to gradient orient_rgb = orientation(gx_rgb, gy_rgb) - @fact all((|).(cos.(gphase_rgb) .* cos.(orient_rgb) .+ - sin.(gphase_rgb) .* sin.(orient_rgb) .< EPS, - (&).(gphase_rgb .== 0.0, orient_rgb .== 0.0))) --> true + @fact all((cos.(gphase_rgb) .* cos.(orient_rgb) .+ + sin.(gphase_rgb) .* sin.(orient_rgb) .< EPS) | + ((gphase_rgb .== 0.0) & (orient_rgb .== 0.0))) --> true "test vrd5mw" # this part is where both are zero because there is no gradient ## Checkerboard Image with RBG{Float64} pixels - (gx_rgb, gy_rgb) = imgradients(cb_image_rgb2, method) + (gy_rgb, gx_rgb) = imgradients(cb_rgbf64, method) mag_rgb = magnitude(gx_rgb, gy_rgb) gphase_rgb = phase(gx_rgb, gy_rgb) - @fact (mag_rgb, gphase_rgb) --> magnitude_phase(gx_rgb, gy_rgb) + @fact (mag_rgb, gphase_rgb) --> magnitude_phase(gx_rgb, gy_rgb) "test Z7WRsV" - @fact gx_rgb[1,SZ,1] --> less_than(0.0) # white to black transition - @fact gx_rgb[1,2*SZ,1] --> greater_than(0.0) # black to white transition - @fact gy_rgb[1,1,SZ] --> less_than(0.0) # white to black transition - @fact gy_rgb[1,1,2*SZ] --> greater_than(0.0) # black to white transition + @fact red(gy_rgb[SZ,1]) --> less_than(0.0) "test B5N6ez" # white to black transition + @fact red(gy_rgb[2*SZ,1]) --> greater_than(0.0) "test BevGJV" # black to white transition + @fact red(gx_rgb[1,SZ]) --> less_than(0.0) "test l8bc6e" # white to black transition + @fact red(gx_rgb[1,2*SZ]) --> greater_than(0.0) "test eZ6Plt" # black to white transition - @fact cos(gphase_rgb[1,SZ,1]) - (-1.0) --> less_than(EPS) # increasing left (= pi radians) - @fact cos(gphase_rgb[1,2*SZ,1]) - 1.0 --> less_than(EPS) # increasing right (= 0 radians) - @fact sin(gphase_rgb[1,1,SZ]) - 1.0 --> less_than(EPS) # increasing up (= pi/2 radians) - @fact sin(gphase_rgb[1,1,2*SZ]) - (-1.0) --> less_than(EPS) # increasing down (= -pi/2 radians) + @fact cos(gphase_rgb[1,SZ]) - (-1.0) --> less_than(EPS) "test M3VjU7" # increasing left (= pi radians) + @fact cos(gphase_rgb[1,2*SZ]) - 1.0 --> less_than(EPS) "test AoPGDf" # increasing right (= 0 radians) + @fact sin(gphase_rgb[SZ,1]) - 1.0 --> less_than(EPS) "test ls8xr8" # increasing up (= pi/2 radians) + @fact sin(gphase_rgb[2*SZ,1]) - (-1.0) --> less_than(EPS) "test 49BiFi" # increasing down (= -pi/2 radians) # Test that orientation is perpendicular to gradient orient_rgb = orientation(gx_rgb, gy_rgb) - @fact all((|).(cos.(gphase_rgb) .* cos.(orient_rgb) .+ - sin.(gphase_rgb) .* sin.(orient_rgb) .< EPS, - (&).(gphase_rgb .== 0.0, orient_rgb .== 0.0))) --> true + @fact all((cos.(gphase_rgb) .* cos.(orient_rgb) .+ + sin.(gphase_rgb) .* sin.(orient_rgb) .< EPS) | + ((gphase_rgb .== 0.0) & (orient_rgb .== 0.0))) --> true "test EW5CFV" # this part is where both are zero because there is no gradient end end @@ -279,79 +227,29 @@ facts("Edge") do m = zeros(UInt8, 20,20) for i = -2:2; m[diagind(m,i)] = 0xff; end - m_xy = grayim(m') - m_yx = grayim(m) - m_yx["spatialorder"] = ["y","x"] - - for method in ["sobel", "prewitt", "ando3", "ando4", "ando5", "ando4_sep", "ando5_sep"] + for method in ["sobel", "prewitt", "ando3", "ando4", "ando5"] ## Diagonal array - (agx, agy) = imgradients(m, method) + (agy, agx) = imgradients(m, method) amag = magnitude(agx, agy) agphase = phase(agx, agy) - @fact (amag, agphase) --> magnitude_phase(agx, agy) + @fact magnitude_phase(agx, agy) --> (amag, agphase) "test magphase" - @fact agx[7,9] --> less_than(0.0) # white to black transition - @fact agx[10,8] --> greater_than(0.0) # black to white transition - @fact agy[10,8] --> less_than(0.0) # white to black transition - @fact agy[7,9] --> greater_than(0.0) # black to white transition + @fact agx[7,9] --> less_than(0.0) "test 3cHL0U" # white to black transition + @fact agx[10,8] --> greater_than(0.0) "test ZnqMYL" # black to white transition + @fact agy[10,8] --> less_than(0.0) "test iIZNpr" # white to black transition + @fact agy[7,9] --> greater_than(0.0) "test olF7SY" # black to white transition # Test direction of increasing gradient - @fact abs(agphase[10,8] - pi/4 ) --> less_than(EPS) # lower edge (increasing up-right = pi/4 radians) - @fact abs(agphase[7,9] - (-3pi/4)) --> less_than(EPS) # upper edge (increasing down-left = -3pi/4 radians) + @fact abs(agphase[10,8] - pi/4 ) --> less_than(EPS) "test 2to4XR" # lower edge (increasing up-right = pi/4 radians) + @fact abs(agphase[7,9] - (-3pi/4)) --> less_than(EPS) "test 4n7Fd5" # upper edge (increasing down-left = -3pi/4 radians) # Test that orientation is perpendicular to gradient aorient = orientation(agx, agy) - @fact all((|).(cos.(agphase) .* cos.(aorient) .+ - sin.(agphase) .* sin.(aorient) .< EPS, - (&).(agphase .== 0.0, aorient .== 0.0))) --> true - # this part is where both are zero because there is no gradient - - ## Diagonal Image, row-major order - - (gx, gy) = imgradients(m_xy, method) - mag = magnitude(gx, gy) - gphase = phase(gx, gy) - @fact (mag, gphase) --> magnitude_phase(gx, gy) - - @fact gx[9,7] --> less_than(0.0) # white to black transition - @fact gx[8,10] --> greater_than(0.0) # black to white transition - @fact gy[8,10] --> less_than(0.0) # white to black transition - @fact gy[9,7] --> greater_than(0.0) # black to white transition - - # Test direction of increasing gradient - @fact abs(gphase[8,10] - pi/4 ) --> less_than(EPS) # lower edge (increasing up-right = pi/4 radians) - @fact abs(gphase[9,7] - (-3pi/4)) --> less_than(EPS) # upper edge (increasing down-left = -3pi/4 radians) - - # Test that orientation is perpendicular to gradient - orient = orientation(gx, gy) - @fact all((|).(cos.(gphase) .* cos.(orient) .+ - sin.(gphase) .* sin.(orient) .< EPS, - (&).(gphase .== 0.0, orient .== 0.0))) --> true - # this part is where both are zero because there is no gradient - - ## Diagonal Image, column-major order - - (gx, gy) = imgradients(m_yx, method) - mag = magnitude(gx, gy) - gphase = phase(gx, gy) - @fact (mag, gphase) --> magnitude_phase(gx, gy) - - @fact gx[7,9] --> less_than(0.0) # white to black transition - @fact gx[10,8] --> greater_than(0.0) # black to white transition - @fact gy[10,8] --> less_than(0.0) # white to black transition - @fact gy[7,9] --> greater_than(0.0) # black to white transition - - # Test direction of increasing gradient - @fact abs(gphase[10,8] - pi/4 ) --> less_than(EPS) # lower edge (increasing up-right = pi/4 radians) - @fact abs(gphase[7,9] - (-3pi/4)) --> less_than(EPS) # upper edge (increasing down-left = -3pi/4 radians) - - # Test that orientation is perpendicular to gradient - orient = orientation(gx, gy) - @fact all((|).(cos.(gphase) .* cos.(orient) .+ - sin.(gphase) .* sin.(orient) .< EPS, - (&).(gphase .== 0.0, orient .== 0.0))) --> true - # this part is where both are zero because there is no gradient + @fact all((cos.(agphase) .* cos.(aorient) .+ + sin.(agphase) .* sin.(aorient) .< EPS) | + ((agphase .== 0.0) & (aorient .== 0.0))) --> true "test cYbfiH" + # this part is where both are zero because there is no gradient end end @@ -359,7 +257,7 @@ facts("Edge") do function thin_edges(img) # Get orientation - gx,gy = imgradients(img) + gy,gx = imgradients(img) orient = phase(gx,gy) # Do NMS thinning @@ -385,7 +283,7 @@ facts("Edge") do b = a + c - v2 r = -b/2a - @fact abs(r - 1/6) --> less_than(EPS) + @fact abs(r - 1/6) --> less_than(EPS) "test fmHvvQ" # Location and value at peak peakloc = r*1.35 + 3 @@ -397,30 +295,30 @@ facts("Edge") do test_axis1 = transposed ⊻ !horizontal if test_axis1 - @fact all(t[:,[1,2,4,5]] .== 0) --> true - @fact all(t[:,3] .== peakval) --> true - @fact all(s[:,[1,2,4,5]] .== zero(Graphics.Point)) --> true + @fact all(t[:,[1,2,4,5]] .== 0) --> true "test 4HIP6f" + @fact all(t[:,3] .== peakval) --> true "test ZITaq0" + @fact all(s[:,[1,2,4,5]] .== zero(Graphics.Point)) --> true "test 0JGsoU" else - @fact all(t[[1,2,4,5],:] .== 0) --> true - @fact all(t[3,:] .== peakval) --> true - @fact all(s[[1,2,4,5],:] .== zero(Graphics.Point)) --> true + @fact all(t[[1,2,4,5],:] .== 0) --> true "test OZUJOS" + @fact all(t[3,:] .== peakval) --> true "test IEOYk3" + @fact all(s[[1,2,4,5],:] .== zero(Graphics.Point)) --> true "test pnnCRT" end if transposed if which == :horizontal - @fact [pt.x for pt in s[:,3]] --> [1:5;] - @fact all([pt.y for pt in s[:,3]] .== peakloc) --> true + @fact [pt.x for pt in s[:,3]] --> [1:5;] "test 7uIThT" + @fact all([pt.y for pt in s[:,3]] .== peakloc) --> true "test OTE4OQ" else - @fact all([pt.x for pt in s[3,:]] .== peakloc) --> true - @fact [pt.y for pt in s[3,:]] --> [1:5;] + @fact all([pt.x for pt in s[3,:]] .== peakloc) --> true "test HZSUky" + @fact [pt.y for pt in s[3,:]] --> [1:5;] "test 3PyK02" end else if which == :horizontal - @fact [pt.x for pt in s[3,:]] --> [1:5;] - @fact all([pt.y for pt in s[3,:]] .== peakloc) --> true + @fact [pt.x for pt in s[3,:]] --> [1:5;] "test oydYR7" + @fact all([pt.y for pt in s[3,:]] .== peakloc) --> true "test pwrLbc" else - @fact all([pt.x for pt in s[:,3]] .== peakloc) --> true - @fact [pt.y for pt in s[:,3]] --> [1:5;] + @fact all([pt.x for pt in s[:,3]] .== peakloc) --> true "test 035uAN" + @fact [pt.y for pt in s[:,3]] --> [1:5;] "test A7DGMj" end end end @@ -433,13 +331,7 @@ facts("Edge") do 3.0 5.0 7.0 6.0 5.0 3.0 5.0 7.0 6.0 5.0] - m_xy = grayim(m') - m_yx = grayim(m) - m_yx["spatialorder"] = ["y","x"] - nms_test_horiz_vert(m, :vertical) - nms_test_horiz_vert(m_xy, :vertical) - nms_test_horiz_vert(m_yx, :vertical) end context("Nonmax suppression horizontal edge") do @@ -450,13 +342,8 @@ facts("Edge") do 3.0 5.0 7.0 6.0 5.0 3.0 5.0 7.0 6.0 5.0] m = m' - m_xy = grayim(m') - m_yx = grayim(m) - m_yx["spatialorder"] = ["y","x"] nms_test_horiz_vert(m, :horizontal) - nms_test_horiz_vert(m_xy, :horizontal) - nms_test_horiz_vert(m_yx, :horizontal) end function nms_test_diagonal(img) @@ -484,23 +371,23 @@ function nms_test_diagonal(img) b = a + c - v2 r = -b/2a - @fact (r - 1/6) --> less_than(EPS) + @fact (r - 1/6) --> less_than(EPS) "test xuJ9PL" - transposed = spatialorder(img)[1] == "x" + transposed = false # Location and value at peak x_peak_offset, y_peak_offset = r*fr, -r*fr peakval = a*r^2 + b*r + c - @fact all(diag(data(t))[2:4] .== peakval) --> true # Edge pixels aren't interpolated here - @fact all(t - diagm(diag(data(t))) .== 0) --> true + @fact all(diag(data(t))[2:4] .== peakval) --> true "test UpmYpg" # Edge pixels aren't interpolated here + @fact all(t - diagm(diag(data(t))) .== 0) --> true "test sK2s90" diag_s = copyproperties(s, diagm(diag(data(s)))) - @fact s --> diag_s + @fact s --> diag_s "test 4xwjQB" - @fact all([pt.x for pt in diag(data(s))[2:4]] - ((2:4) + x_peak_offset) .< EPS) --> true - @fact all([pt.y for pt in diag(data(s))[2:4]] - ((2:4) + y_peak_offset) .< EPS) --> true + @fact all([pt.x for pt in diag(data(s))[2:4]] - ((2:4) + x_peak_offset) .< EPS) --> true "test luz17v" + @fact all([pt.y for pt in diag(data(s))[2:4]] - ((2:4) + y_peak_offset) .< EPS) --> true "test RYzBTz" end @@ -512,13 +399,7 @@ end 0.0 3.0 5.0 7.0 6.0 0.0 0.0 3.0 5.0 7.0] - m_xy = grayim(m') - m_yx = grayim(m) - m_yx["spatialorder"] = ["y","x"] - nms_test_diagonal(m) - nms_test_diagonal(m_xy) - nms_test_diagonal(m_yx) end end From 9cf9f411eac9e08d69277417dea8bb755da6a728 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 24 Sep 2016 11:26:52 -0500 Subject: [PATCH 08/38] Introduce NumberLike and RealLike typealiases to simplify code We may want to reduce to just one of these, but there are differences (RealLike implies the existence of a total order, whereas NumberLike does not). For now this already represents some centralization. --- src/Images.jl | 6 ++---- src/edge.jl | 11 ++++------- src/exposure.jl | 20 ++++++++++---------- src/map.jl | 4 ++-- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index e2c488e8..670670a0 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -36,14 +36,12 @@ import Colors: Fractional, red, green, blue typealias AbstractGray{T} Color{T,1} typealias TransparentRGB{C<:AbstractRGB,T} TransparentColor{C,T,4} typealias TransparentGray{C<:AbstractGray,T} TransparentColor{C,T,2} +typealias NumberLike Union{Number,AbstractGray} +typealias RealLike Union{Real,AbstractGray} import Graphics import Graphics: width, height, Point using StatsBase # TODO: eliminate this dependency -# if isdefined(module_parent(Images), :Grid) -# import ..Grid.restrict -# end - const is_little_endian = ENDIAN_BOM == 0x04030201 @reexport using ImageCore diff --git a/src/edge.jl b/src/edge.jl index b3b20466..31faa28d 100644 --- a/src/edge.jl +++ b/src/edge.jl @@ -1,8 +1,5 @@ ### Edge and Gradient Related Image Operations ### -typealias GrayLike Union{Number,AbstractGray} - - # Phase (angle of steepest gradient ascent), calculated from X and Y gradient images """ phase(grad_x, grad_y) -> p @@ -15,11 +12,11 @@ function phase{T<:Number}(grad_x::T, grad_y::T, tol=sqrt(eps(T))) atan2(-grad_y, grad_x) * ((abs(grad_x) > tol) | (abs(grad_y) > tol)) end phase(grad_x::Number, grad_y::Number) = phase(promote(grad_x, grad_y)...) -phase(grad_x::GrayLike, grad_y::GrayLike) = phase(gray(grad_x), gray(grad_y)) +phase(grad_x::NumberLike, grad_y::NumberLike) = phase(gray(grad_x), gray(grad_y)) phase(grad_x::AbstractRGB, grad_y::AbstractRGB) = phase(vecsum(grad_x), vecsum(grad_y)) -magnitude_phase(grad_x::GrayLike, grad_y::GrayLike) = +magnitude_phase(grad_x::NumberLike, grad_y::NumberLike) = hypot(grad_x, grad_y), phase(grad_x, grad_y) function magnitude_phase(grad_x::AbstractRGB, grad_y::AbstractRGB) @@ -43,7 +40,7 @@ function orientation{T<:Number}(grad_x::T, grad_y::T, tol=sqrt(eps(T))) atan2(grad_x, grad_y) * ((abs(grad_x) > tol) | (abs(grad_y) > tol)) end orientation(grad_x::Number, grad_y::Number) = orientation(promote(grad_x, grad_y)...) -orientation(grad_x::GrayLike, grad_y::GrayLike) = orientation(gray(grad_x), gray(grad_y)) +orientation(grad_x::NumberLike, grad_y::NumberLike) = orientation(gray(grad_x), gray(grad_y)) orientation(grad_x::AbstractRGB, grad_y::AbstractRGB) = orientation(vecsum(grad_x), vecsum(grad_y)) @@ -406,7 +403,7 @@ Parameters : as quantiles or absolute values """ -function canny{T<:Union{Number,Gray}}(img_gray::AbstractMatrix{T}, sigma::Number = 1.4, upperThreshold::Number = 0.90, lowerThreshold::Number = 0.10; percentile::Bool = true) +function canny{T<:NumberLike}(img_gray::AbstractMatrix{T}, sigma::Number = 1.4, upperThreshold::Number = 0.90, lowerThreshold::Number = 0.10; percentile::Bool = true) img_grayf = imfilter_gaussian(img_gray, [sigma,sigma]) img_grad_y, img_grad_x = imgradients(img_grayf, KernelFactors.sobel) img_mag, img_phase = magnitude_phase(img_grad_x, img_grad_y) diff --git a/src/exposure.jl b/src/exposure.jl index de1a9175..f9be2a19 100644 --- a/src/exposure.jl +++ b/src/exposure.jl @@ -35,7 +35,7 @@ complement(x::TransparentColor) = typeof(x)(complement(color(x)), alpha(x)) imhist{T<:Colorant}(img::AbstractArray{T}, nbins::Integer = 400) = imhist(convert(Array{Gray}, data(img)), nbins) -function imhist{T<:Union{Gray,Number}}(img::AbstractArray{T}, nbins::Integer = 400) +function imhist{T<:NumberLike}(img::AbstractArray{T}, nbins::Integer = 400) minval = minfinite(img) maxval = maxfinite(img) imhist(img, nbins, minval, maxval) @@ -57,7 +57,7 @@ maximum values present in the image are taken. `count[end]` is the number satisfying `x >= edges[end]`. Consequently, `length(count) == length(edges)+1`. """ -function imhist(img::AbstractArray, nbins::Integer, minval::Union{Gray,Real}, maxval::Union{Gray,Real}) +function imhist(img::AbstractArray, nbins::Integer, minval::RealLike, maxval::RealLike) edges = StatsBase.histrange([Float64(minval), Float64(maxval)], nbins, :left) imhist(img, edges) end @@ -78,7 +78,7 @@ function imhist(img::AbstractArray, edges::Range) edges, histogram end -function _histeq_pixel_rescale{T<:Union{Gray,Number}}(pixel::T, cdf, minval, maxval) +function _histeq_pixel_rescale{T<:NumberLike}(pixel::T, cdf, minval, maxval) n = length(cdf) bin_pixel = clamp(ceil(Int, (pixel - minval) * length(cdf) / (maxval - minval)), 1, n) rescaled_pixel = minval + ((cdf[bin_pixel] - cdf[1]) * (maxval - minval) / (cdf[end] - cdf[1])) @@ -114,7 +114,7 @@ If minval and maxval are specified then intensities are equalized to the range (minval, maxval). The default values are 0 and 1. """ -function histeq(img::AbstractArray, nbins::Integer, minval::Union{Number,Gray}, maxval::Union{Number,Gray}) +function histeq(img::AbstractArray, nbins::Integer, minval::RealLike, maxval::RealLike) bins, histogram = imhist(img, nbins, minval, maxval) cdf = cumsum(histogram[2:end-1]) img_shape = size(img) @@ -132,7 +132,7 @@ function histeq(img::AbstractArray, nbins::Integer) histeq(img, nbins, zero(T), one(T)) end -function histeq(img::AbstractImage, nbins::Integer, minval::Union{Number,Gray}, maxval::Union{Number,Gray}) +function histeq(img::AbstractImage, nbins::Integer, minval::RealLike, maxval::RealLike) newimg = histeq(data(img), nbins, minval, maxval) shareproperties(img, newimg) end @@ -141,7 +141,7 @@ histeq(img::AbstractImage, nbins::Integer) = shareproperties(img, histeq(data(im adjust_gamma(img::AbstractImage, gamma::Number) = shareproperties(img, adjust_gamma(data(img), gamma)) -_gamma_pixel_rescale{T<:Union{Gray, Number}}(pixel::T, gamma::Number) = pixel ^ gamma +_gamma_pixel_rescale{T<:NumberLike}(pixel::T, gamma::Number) = pixel ^ gamma function _gamma_pixel_rescale{C<:Color}(pixel::C, gamma::Number) yiq = convert(YIQ, pixel) @@ -217,7 +217,7 @@ matched and `oimg` is the image having the desired histogram to be matched to. """ histmatch(img::AbstractImage, oimg::AbstractArray, nbins::Integer = 400) = shareproperties(img, histmatch(data(img), oimg, nbins)) -_hist_match_pixel{T<:Union{Gray, Number}}(pixel::T, bins, lookup_table) = T(bins[lookup_table[searchsortedlast(bins, pixel)]]) +_hist_match_pixel{T<:NumberLike}(pixel::T, bins, lookup_table) = T(bins[lookup_table[searchsortedlast(bins, pixel)]]) function _hist_match_pixel{T<:Color}(pixel::T, bins, lookup_table) yiq = convert(YIQ, pixel) @@ -394,16 +394,16 @@ function _clahe{C}(img::AbstractArray{C, 2}, nbins::Integer = 100, xblocks::Inte res_img end -_clahe_pixel_rescale{T<:Union{Gray, Number}}(pixel::T, cdf, edges) = cdf[searchsortedlast(edges, pixel, Base.Order.Forward)] +_clahe_pixel_rescale{T<:NumberLike}(pixel::T, cdf, edges) = cdf[searchsortedlast(edges, pixel, Base.Order.Forward)] -function _clahe_pixel_rescale{T<:Union{Gray, Number}}(pixel::T, first, second, edges, pos, length) +function _clahe_pixel_rescale{T<:NumberLike}(pixel::T, first, second, edges, pos, length) id = searchsortedlast(edges, pixel, Base.Order.Forward) f = first[id] s = second[id] T(((length - pos) * f + (pos - 1) * s) / (length - 1)) end -function _clahe_pixel_rescale{T<:Union{Gray, Number}}(pixel::T, top_left, top_right, bot_left, bot_right, edges, i, j, w, h) +function _clahe_pixel_rescale{T<:NumberLike}(pixel::T, top_left, top_right, bot_left, bot_right, edges, i, j, w, h) id = searchsortedlast(edges, pixel, Base.Order.Forward) tl = top_left[id] tr = top_right[id] diff --git a/src/map.jl b/src/map.jl index 781420bf..fcef6f92 100644 --- a/src/map.jl +++ b/src/map.jl @@ -258,12 +258,12 @@ ScaleMinMax{To<:Colorant,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = similar{T,F,To,From,S}(mapi::ScaleMinMax{To,From,S}, ::Type{T}, ::Type{F}) = ScaleMinMax{T,F,S}(convert(F,mapi.min), convert(F.mapi.max), mapi.s) # Implementation -function immap{To<:Union{Real,AbstractGray},From<:Union{Real,AbstractGray}}(mapi::ScaleMinMax{To,From}, val::From) +function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::From) t = clamp(gray(val), gray(mapi.min), gray(mapi.max)) f = mapi.s*t - mapi.s*mapi.min # better than mapi.s*(t-mapi.min) (overflow) convertsafely(To, f) end -function immap{To<:Union{Real,AbstractGray},From<:Union{Real,AbstractGray}}(mapi::ScaleMinMax{To,From}, val::Union{Real,Colorant}) +function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::Union{Real,Colorant}) immap(mapi, convert(From, val)) end function map1{To<:Union{RGB24,ARGB32},From<:Real}(mapi::ScaleMinMax{To,From}, val::From) From fdcbb03885cbb7ecf53dcde668d4fd558ac2ec24 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 24 Sep 2016 17:07:44 -0500 Subject: [PATCH 09/38] Get the corner tests working --- src/corner.jl | 69 ++++++++++++++++++++++++++++------------------ src/deprecated.jl | 4 ++- test/old/corner.jl | 6 ++-- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/corner.jl b/src/corner.jl index a3ad21f0..aee18697 100644 --- a/src/corner.jl +++ b/src/corner.jl @@ -16,8 +16,7 @@ specified, the values of the responses are thresholded to give the corner pixels The threshold is assumed to be a percentile value unless `percentile` is set to false. """ function imcorner(img::AbstractArray; method::Function = harris, args...) - img_gray = convert(Array{Gray}, img) - responses = method(img_gray; args...) + responses = method(img; args...) corners = falses(size(img)) maxima = map(CartesianIndex{2}, findlocalmaxima(responses)) for m in maxima corners[m] = true end @@ -25,11 +24,10 @@ function imcorner(img::AbstractArray; method::Function = harris, args...) end function imcorner(img::AbstractArray, threshold, percentile; method::Function = harris, args...) - img_gray = convert(Array{Gray}, img) - responses = method(img_gray; args...) + responses = method(img; args...) if percentile == true - threshold = StatsBase.percentile(responses[:], threshold * 100) + threshold = StatsBase.percentile(vec(responses), threshold * 100) end corners = map(i -> i > threshold, responses) @@ -73,29 +71,45 @@ Performs Kitchen Rosenfeld corner detection. The covariances can be taken using weighted filter or a gamma kernel. """ function kitchen_rosenfeld(img::AbstractArray; border::AbstractString = "replicate") - (grad_x, grad_y) = imgradients(img, "sobel", border) - (grad_xx, grad_xy) = imgradients(grad_x, "sobel", border) - (grad_yx, grad_yy) = imgradients(grad_y, "sobel", border) - numerator = map((x, y, xx, xy, yy) -> xx * (y ^ 2) + yy * (x ^ 2) - 2 * xy * x * y, grad_x, grad_y, grad_xx, grad_xy, grad_yy) - denominator = map((x, y) -> x ^ 2 + y ^ 2, grad_x, grad_y) - corner = map((n, d) -> d == 0.0 ? 0.0 : -n / d, numerator, denominator) - corner + meth = KernelFactors.sobel + (grad_x, grad_y) = imgradients(img, meth, border) + (grad_xx, grad_xy) = imgradients(grad_x, meth, border) + (grad_yx, grad_yy) = imgradients(grad_y, meth, border) + map(kr, grad_x, grad_y, grad_xx, grad_xy, grad_yy) +end + +function kr{T<:Real}(x::T, y::T, xx::T, xy::T, yy::T) + num = xx*y*y + yy*x*x - 2*xy*x*y + denom = x*x + y*y + ifelse(denom == 0, zero(num)/one(denom), -num/denom) +end + +function kr(x::Real, y::Real, xx::Real, xy::Real, yy::Real) + xp, yp, xxp, xyp, yyp = promote(x, y, xx, xy, yy) + kr(xp, yp, xxp, xyp, yyp) +end + +kr(x::RealLike, y::RealLike, xx::RealLike, xy::RealLike, yy::RealLike) = + kr(gray(x), gray(y), gray(xx), gray(xy), gray(yy)) + +function kr(x::AbstractRGB, y::AbstractRGB, xx::AbstractRGB, xy::AbstractRGB, yy::AbstractRGB) + krrgb = RGB(kr(red(x), red(y), red(xx), red(xy), red(yy)), + kr(green(x), green(y), green(xx), green(xy), green(yy)), + kr(blue(x), blue(y), blue(xx), blue(xy), blue(yy))) + gray(convert(Gray, krrgb)) end """ -``` -corners = fastcorners(img, n, threshold) -``` + fastcorners(img, n, threshold) -> corners Performs FAST Corner Detection. `n` is the number of contiguous pixels which need to be greater (lesser) than intensity + threshold (intensity - threshold) for a pixel to be marked as a corner. The default value for n is 12. """ function fastcorners{T}(img::AbstractArray{T}, n::Int = 12, threshold::Float64 = 0.15) - img_padded = padarray(img, [3,3], [3,3], "value", 0) + img_padded = padarray(img, Fill(0, (3,3))) corner = falses(size(img)) - h, w = size(img) - R = CartesianRange(CartesianIndex((4, 4)), CartesianIndex((h + 3, w + 3))) + R = CartesianRange(size(img)) idx = map(CartesianIndex{2}, [(0, 3), (1, 3), (2, 2), (3, 1), (3, 0), (3, -1), (2, -2), (1, -3), (0, -3), (-1, -3), (-2, -2), (-3, -1), (-3, 0), (-3, 1), (-2, 2), (-1, 3)]) @@ -133,7 +147,7 @@ function fastcorners{T}(img::AbstractArray{T}, n::Int = 12, threshold::Float64 = end if consecutive_dark == n || consecutive_bright == n - corner[I - CartesianIndex((3, 3))] = true + corner[I] = true break end end @@ -142,18 +156,18 @@ function fastcorners{T}(img::AbstractArray{T}, n::Int = 12, threshold::Float64 = end function gradcovs(img::AbstractArray, border::AbstractString = "replicate"; weights::Function = meancovs, args...) - (grad_x, grad_y) = imgradients(img, "sobel", border) + (grad_x, grad_y) = imgradients(img, KernelFactors.sobel, border) - cov_xx = grad_x .* grad_x - cov_xy = grad_x .* grad_y - cov_yy = grad_y .* grad_y + cov_xx = dotc.(grad_x, grad_x) + cov_xy = dotc.(grad_x, grad_y) + cov_yy = dotc.(grad_y, grad_y) weights(cov_xx, cov_xy, cov_yy, args...) end function meancovs(cov_xx, cov_xy, cov_yy, blockSize::Int = 3) - box_filter_kernel = (1 / (blockSize * blockSize)) * ones(blockSize, blockSize) + box_filter_kernel = centered((1 / (blockSize * blockSize)) * ones(blockSize, blockSize)) filt_cov_xx = imfilter(cov_xx, box_filter_kernel) filt_cov_xy = imfilter(cov_xy, box_filter_kernel) @@ -163,10 +177,11 @@ function meancovs(cov_xx, cov_xy, cov_yy, blockSize::Int = 3) end function gammacovs(cov_xx, cov_xy, cov_yy, gamma::Float64 = 1.4) + kernel = KernelFactors.gaussian((gamma, gamma)) - filt_cov_xx = imfilter_gaussian(cov_xx, [gamma, gamma]) - filt_cov_xy = imfilter_gaussian(cov_xy, [gamma, gamma]) - filt_cov_yy = imfilter_gaussian(cov_yy, [gamma, gamma]) + filt_cov_xx = imfilter(cov_xx, kernel) + filt_cov_xy = imfilter(cov_xy, kernel) + filt_cov_yy = imfilter(cov_yy, kernel) filt_cov_xx, filt_cov_xy, filt_cov_yy end diff --git a/src/deprecated.jl b/src/deprecated.jl index a5bcf878..1bcc56af 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -12,7 +12,9 @@ rerange!(args...) = error("reslice! has been removed, along with SliceData; plea @deprecate ando3 KernelFactors.ando3 @deprecate ando4 KernelFactors.ando3 @deprecate ando5 KernelFactors.ando3 -@deprecate gaussian2d KernelFactors.gaussian +@deprecate gaussian2d() Kernel.gaussian(0.5) +@deprecate gaussian2d(σ::Number) Kernel.gaussian(σ) +@deprecate gaussian2d(σ::Number, filter_size) Kernel.gaussian((σ,σ), (filter_size...,)) @deprecate imaverage KernelFactors.boxcar @deprecate imdog Kernel.DoG @deprecate imlog Kernel.LoG diff --git a/test/old/corner.jl b/test/old/corner.jl index 46b21040..5671c472 100644 --- a/test/old/corner.jl +++ b/test/old/corner.jl @@ -113,7 +113,7 @@ facts("Corner") do @fact all(Ac[12:28, 12:28]) --> false end - context("Fast Corners") do + context("Fast Corners") do img = reshape(1:1:49, 7, 7) @@ -152,7 +152,7 @@ facts("Corner") do corners[4, end - 1] = true @fact all(corners) --> true - img = gaussian2d(1.4) + img = parent(gaussian2d(1.4)) img = vcat(img, img) img = hcat(img, img) corners = fastcorners(img, 12, 0.05) @@ -161,7 +161,7 @@ facts("Corner") do @fact corners[5, 14] --> true @fact corners[14, 5] --> true @fact corners[14, 14] --> true - + @fact all(corners[:, 1:3] .== false) --> true @fact all(corners[1:3, :] .== false) --> true @fact all(corners[:, 16:end] .== false) --> true From 74a49d20bb8663ff0337d5e7d8f27a0208d4371b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 26 Sep 2016 07:03:44 -0500 Subject: [PATCH 10/38] Eliminate most deprecated names from src writemime is the exception --- src/Images.jl | 30 ++---------------------------- src/algorithms.jl | 4 ++-- src/deprecated.jl | 15 +++++++++++++++ src/exposure.jl | 12 ++++++------ src/map.jl | 29 ++++++++--------------------- 5 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index 670670a0..e0401e12 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -1,4 +1,4 @@ -__precompile__(false) # because of ImageAxes/ImageMeta +__precompile__(true) # because of ImageAxes/ImageMeta module Images @@ -41,6 +41,7 @@ typealias RealLike Union{Real,AbstractGray} import Graphics import Graphics: width, height, Point using StatsBase # TODO: eliminate this dependency +using IndirectArrays const is_little_endian = ENDIAN_BOM == 0x04030201 @@ -66,11 +67,6 @@ include("distances.jl") include("deprecated.jl") export # types - AbstractImage, - AbstractImageDirect, - AbstractImageIndexed, - Image, - ImageCmap, BitShift, ClampMin, ClampMax, @@ -91,12 +87,6 @@ export # types # macros @test_approx_eq_sigma_eps, - # constants - palette_fire, - palette_gray32, - palette_gray64, - palette_rainbow, - # core functions assert2d, assert_scalar_color, @@ -105,11 +95,9 @@ export # types assert_yfirst, colordim, colorspace, - coords, coords_spatial, copyproperties, data, - dimindexes, getindexim, grayim, colorim, @@ -144,25 +132,13 @@ export # types widthheight, raw, - # iterator functions - first_index, - iterate_spatial, - parent, - # color-related functions - indexedcolor, - lut, separate, - uint32color, - uint32color!, # Scaling of intensity sc, scale, mapinfo, - uint8sc, - uint16sc, - uint32sc, ufixed8sc, ufixedsc, @@ -223,10 +199,8 @@ export # types orientation, padarray, phase, - prewitt, sad, sadn, - sobel, ssd, ssdn, thin_edges, diff --git a/src/algorithms.jl b/src/algorithms.jl index 745c826f..05eebf21 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -785,7 +785,7 @@ default is 8-connectivity in 2d, 27-connectivity in 3d, etc. You can specify the list of dimensions that you want to include in the connectivity, e.g., `region = [1,2]` would exclude the third dimension from filtering. """ -dilate(img::AbstractImageDirect, region=coords_spatial(img)) = shareproperties(img, dilate!(copy(data(img)), region)) +dilate(img::ImageMeta, region=coords_spatial(img)) = shareproperties(img, dilate!(copy(data(img)), region)) """ ``` imge = erode(img, [region]) @@ -796,7 +796,7 @@ default is 8-connectivity in 2d, 27-connectivity in 3d, etc. You can specify the list of dimensions that you want to include in the connectivity, e.g., `region = [1,2]` would exclude the third dimension from filtering. """ -erode(img::AbstractImageDirect, region=coords_spatial(img)) = shareproperties(img, erode!(copy(data(img)), region)) +erode(img::ImageMeta, region=coords_spatial(img)) = shareproperties(img, erode!(copy(data(img)), region)) dilate(img::AbstractArray, region=coords_spatial(img)) = dilate!(copy(img), region) erode(img::AbstractArray, region=coords_spatial(img)) = erode!(copy(img), region) diff --git a/src/deprecated.jl b/src/deprecated.jl index 1bcc56af..444f6588 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -1,3 +1,9 @@ +export + AbstractImage, + AbstractImageDirect, + AbstractImageIndexed, + Image + const yx = ["y", "x"] const xy = ["x", "y"] @@ -5,6 +11,15 @@ SliceData(args...) = error("SliceData has been removed, please use julia's regul reslice!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") rerange!(args...) = error("reslice! has been removed, along with SliceData; please use julia's regular indexing operations") +# These should have been deprecated long ago +@deprecate uint32color(img) immap(mapinfo(UInt32, img), img) +@deprecate uint32color!(buf, img::AbstractArray) map!(mapinfo(UInt32, img), buf, img) +@deprecate uint32color!(buf, img::AbstractArray, mi::MapInfo) map!(mi, buf, img) +@deprecate uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractArray{T,N}) map!(mapinfo(UInt32, img), buf, img) +@deprecate uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::ChannelView{T,N1}) map!(mapinfo(UInt32, img), buf, img, Val{1}) +@deprecate uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractArray{T,N}, mi::MapInfo) map!(mi, buf, img) +@deprecate uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::ChannelView{T,N1}, mi::MapInfo) map!(mi, buf, img, Val{1}) + @deprecate flipx(img) flipdim(img, 2) @deprecate flipy(img) flipdim(img, 1) @deprecate flipz(img) flipdim(img, 3) diff --git a/src/exposure.jl b/src/exposure.jl index f9be2a19..0d4d1a0f 100644 --- a/src/exposure.jl +++ b/src/exposure.jl @@ -29,7 +29,7 @@ Returns the complement of an image. """ imcomplement{T}(img::AbstractArray{T}) = map(complement, img) -imcomplement(img::AbstractImage) = copyproperties(img, imcomplement(data(img))) +imcomplement(img::ImageMeta) = copyproperties(img, imcomplement(data(img))) complement(x) = one(x)-x complement(x::TransparentColor) = typeof(x)(complement(color(x)), alpha(x)) @@ -132,14 +132,14 @@ function histeq(img::AbstractArray, nbins::Integer) histeq(img, nbins, zero(T), one(T)) end -function histeq(img::AbstractImage, nbins::Integer, minval::RealLike, maxval::RealLike) +function histeq(img::ImageMeta, nbins::Integer, minval::RealLike, maxval::RealLike) newimg = histeq(data(img), nbins, minval, maxval) shareproperties(img, newimg) end -histeq(img::AbstractImage, nbins::Integer) = shareproperties(img, histeq(data(img), nbins)) +histeq(img::ImageMeta, nbins::Integer) = shareproperties(img, histeq(data(img), nbins)) -adjust_gamma(img::AbstractImage, gamma::Number) = shareproperties(img, adjust_gamma(data(img), gamma)) +adjust_gamma(img::ImageMeta, gamma::Number) = shareproperties(img, adjust_gamma(data(img), gamma)) _gamma_pixel_rescale{T<:NumberLike}(pixel::T, gamma::Number) = pixel ^ gamma @@ -215,7 +215,7 @@ Returns a grayscale histogram matched image with a granularity of `nbins` number matched and `oimg` is the image having the desired histogram to be matched to. """ -histmatch(img::AbstractImage, oimg::AbstractArray, nbins::Integer = 400) = shareproperties(img, histmatch(data(img), oimg, nbins)) +histmatch(img::ImageMeta, oimg::AbstractArray, nbins::Integer = 400) = shareproperties(img, histmatch(data(img), oimg, nbins)) _hist_match_pixel{T<:NumberLike}(pixel::T, bins, lookup_table) = T(bins[lookup_table[searchsortedlast(bins, pixel)]]) @@ -287,7 +287,7 @@ function clahe{C}(img::AbstractArray{C, 2}, nbins::Integer = 100; xblocks::Integ imresize(hist_equalised_img, (h, w)) end -function clahe(img::AbstractImage, nbins::Integer = 100; xblocks::Integer = 8, yblocks::Integer = 8, clip::Number = 3) +function clahe(img::ImageMeta, nbins::Integer = 100; xblocks::Integer = 8, yblocks::Integer = 8, clip::Number = 3) shareproperties(clahe(data(img), nbins, xblocks = xblocks, yblocks = yblocks, clip = clip), img) end diff --git a/src/map.jl b/src/map.jl index fcef6f92..6687ab42 100644 --- a/src/map.jl +++ b/src/map.jl @@ -483,10 +483,10 @@ end # out # note this isn't type-stable # end -function immap{T<:Colorant}(mapi::MapInfo{T}, img::AbstractImageIndexed) - out = Image(Array(T, size(img)), properties(img)) - map!(mapi, out, img) -end +# function immap{T<:Colorant}(mapi::MapInfo{T}, img::AbstractImageIndexed) +# out = Image(Array(T, size(img)), properties(img)) +# map!(mapi, out, img) +# end map!{T,T1,T2,N}(mapi::MapInfo{T1}, out::AbstractArray{T,N}, img::AbstractArray{T2,N}) = _map_a!(mapi, out, img) @@ -511,9 +511,9 @@ take(mapi::MapInfo, img::AbstractArray) = mapi take{T}(mapi::ScaleAutoMinMax{T}, img::AbstractArray) = ScaleMinMax(T, img) # Indexed images (colormaps) -map!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexed{T1,N}) = +map!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::IndirectArray{T1,N}) = _mapindx!(mapi, out, img) -function _mapindx!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexed{T1,N}) +function _mapindx!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::IndirectArray{T1,N}) dimg = data(img) dout = data(out) colmap = immap(mapi, dimg.values) @@ -650,19 +650,6 @@ mapinfo(::Type{Clamp}, img::AbstractArray{RGB24}) = MapNone{RGB{UFixed8}}() mapinfo(::Type{Clamp}, img::AbstractArray{ARGB32}) = MapNone{BGRA{UFixed8}}() -# Backwards-compatibility -uint32color(img) = immap(mapinfo(UInt32, img), img) -uint32color!(buf, img::AbstractArray) = map!(mapinfo(UInt32, img), buf, img) -uint32color!(buf, img::AbstractArray, mi::MapInfo) = map!(mi, buf, img) -uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N}) = - map!(mapinfo(UInt32, img), buf, img) -uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N1}) = - map!(mapinfo(UInt32, img), buf, img, Val{colordim(img)}) -uint32color!{T,N}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N}, mi::MapInfo) = - map!(mi, buf, img) -uint32color!{T,N,N1}(buf::Array{UInt32,N}, img::AbstractImageDirect{T,N1}, mi::MapInfo) = - map!(mi, buf, img, Val{colordim(img)}) - """ ``` imgsc = sc(img) @@ -674,5 +661,5 @@ Applies default or specified `ScaleMinMax` mapping to the image. sc(img::AbstractArray) = immap(ScaleMinMax(UFixed8, img), img) sc(img::AbstractArray, mn::Real, mx::Real) = immap(ScaleMinMax(UFixed8, img, mn, mx), img) -ufixedsc{T<:UFixed}(::Type{T}, img::AbstractImageDirect) = immap(mapinfo(T, img), img) -ufixed8sc(img::AbstractImageDirect) = ufixedsc(UFixed8, img) +ufixedsc{T<:UFixed}(::Type{T}, img::AbstractArray) = immap(mapinfo(T, img), img) +ufixed8sc(img::AbstractArray) = ufixedsc(UFixed8, img) From e565f57eb367224405f462ac743209d52d91ce30 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 26 Sep 2016 07:04:32 -0500 Subject: [PATCH 11/38] Fix a method-sorting bug (immap) and an ambiguity (restrict) --- src/algorithms.jl | 2 ++ src/map.jl | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 05eebf21..28d6d8f4 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -514,6 +514,8 @@ along the dimensions listed in `region`, or all spatial coordinates if is better than a naive summation over 2x2 blocks. """ restrict(img::AbstractArray, ::Tuple{}) = img +restrict(img::AxisArray, ::Tuple{}) = img +restrict(img::ImageMeta, ::Tuple{}) = img typealias RegionType Union{Dims,Vector{Int}} diff --git a/src/map.jl b/src/map.jl index 6687ab42..8568bfdf 100644 --- a/src/map.jl +++ b/src/map.jl @@ -258,14 +258,14 @@ ScaleMinMax{To<:Colorant,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = similar{T,F,To,From,S}(mapi::ScaleMinMax{To,From,S}, ::Type{T}, ::Type{F}) = ScaleMinMax{T,F,S}(convert(F,mapi.min), convert(F.mapi.max), mapi.s) # Implementation -function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::From) +function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::Union{Real,Colorant}) t = clamp(gray(val), gray(mapi.min), gray(mapi.max)) f = mapi.s*t - mapi.s*mapi.min # better than mapi.s*(t-mapi.min) (overflow) convertsafely(To, f) end -function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::Union{Real,Colorant}) - immap(mapi, convert(From, val)) -end +# function immap{To<:RealLike,From<:RealLike}(mapi::ScaleMinMax{To,From}, val::Union{Real,Colorant}) +# immap(mapi, convert(From, val)) +# end function map1{To<:Union{RGB24,ARGB32},From<:Real}(mapi::ScaleMinMax{To,From}, val::From) t = clamp(val, mapi.min, mapi.max) f = mapi.s*t - mapi.s*mapi.min From aeac21cabb2f66e78b57285029021cf8be40284d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 26 Sep 2016 07:06:25 -0500 Subject: [PATCH 12/38] Fix an eltype problem from changes in promotion rules in colors --- test/old/algorithms.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index 13c7bbcc..35e04f96 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -241,11 +241,11 @@ facts("Algorithms") do @fact B --> roughly(Btarget) "test g0lXjp" Argb = reinterpret(RGB, reinterpret(UFixed16, permutedims(A, (3,1,2)))) B = Images.restrict(Argb) - Bf = permutedims(reinterpret(Float64, B), (2,3,1)) + Bf = permutedims(reinterpret(eltype(eltype(B)), B), (2,3,1)) @fact Bf --> roughly(Btarget/reinterpret(one(UFixed16)), 1e-12) "test IVByaq" Argba = reinterpret(RGBA{UFixed16}, reinterpret(UFixed16, A)) B = Images.restrict(Argba) - @fact reinterpret(Float64, B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(UFixed16)), 1e-12) "test z8K24e" + @fact reinterpret(eltype(eltype(B)), B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(UFixed16)), 1e-12) "test z8K24e" A = reshape(1:60, 5, 4, 3) B = Images.restrict(A, (1,2,3)) @fact cat(3, [ 2.6015625 8.71875 6.1171875; From 6ca7aa0f34d900ac3db59d2979366bddfa7bf564 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 26 Sep 2016 07:09:44 -0500 Subject: [PATCH 13/38] Fix parallel test --- test/old/parallel.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/old/parallel.jl b/test/old/parallel.jl index f2a720c3..11d5e9b4 100644 --- a/test/old/parallel.jl +++ b/test/old/parallel.jl @@ -12,9 +12,9 @@ end @everywhere function test287(img) return 0; end -Imgs = Array(Images.Image{RGB{Float64}}, 2); -Imgs[1] = convert(Images.Image{RGB}, rand(100, 100, 3)); -Imgs[2] = convert(Images.Image{RGB}, rand(100, 100, 3)); +Imgs = Array{Matrix{RGB{Float64}}}(2); +Imgs[1] = rand(RGB{Float64}, 100, 100) +Imgs[2] = rand(RGB{Float64}, 100, 100) let Imgs = Imgs; ret = pmap(i -> test287(Imgs[i]), 1:2); From 3c034fc9c46eb587995537642fe415253d40614a Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 27 Sep 2016 06:07:09 -0500 Subject: [PATCH 14/38] Fix a (new) ambiguous-Dict error That method should not have been defined in the first place --- src/core.jl | 1469 ---------------------------------------- test/old/algorithms.jl | 2 +- 2 files changed, 1 insertion(+), 1470 deletions(-) delete mode 100644 src/core.jl diff --git a/src/core.jl b/src/core.jl deleted file mode 100644 index f0ec88c4..00000000 --- a/src/core.jl +++ /dev/null @@ -1,1469 +0,0 @@ -#### Types and constructors #### - -# Plain arrays can be treated as images. Other types will have -# metadata associated, make yours a child of one of the following: -abstract AbstractImage{T,N} <: AbstractArray{T,N} # image with metadata -""" -`AbstractImageDirect` is the supertype of all images where pixel -values are stored directly in an `AbstractArray`. See also -`AbstractImageIndexed`. -""" -abstract AbstractImageDirect{T,N} <: AbstractImage{T,N} -""" -`AbstractImageIndexed` is the supertype of all "colormap" images, -where pixel values are accessed from a lookup table. See also -`AbstractImageDirect`. -""" -abstract AbstractImageIndexed{T,N} <: AbstractImage{T,N} - -""" -``` -Image(data, [properties]) -Image(data, prop1=val1, prop2=val2, ...) -``` -creates a new "direct" image, one in which the values in `data` -correspond to the pixel values. In contrast with `convert`, `grayim` -and `colorim`, this does not permute the data array or attempt to -guess any of the `properties`. If `data` encodes color information -along one of the dimensions of the array (as opposed to using a -`Color` array, from the `Colors.jl` package), be sure to specify the -`"colordim"` and `"colorspace"` in `properties`. -""" -type Image{T,N,A<:AbstractArray} <: AbstractImageDirect{T,N} - data::A - properties::Dict{Compat.ASCIIString,Any} -end -Image(data::AbstractArray, props::Dict) = Image{eltype(data),ndims(data),typeof(data)}(data,props) -Image{T,N}(data::AbstractArray{T,N}, props::Dict) = Image{T,N,typeof(data)}(data,props) -Image(data::AbstractArray; kwargs...) = Image(data, kwargs2dict(kwargs)) - -""" -``` -ImageCmap(data, cmap, [properties]) -``` -creates an indexed (colormap) image. -""" -type ImageCmap{T<:Colorant,N,A<:AbstractArray} <: AbstractImageIndexed{T,N} - data::A - cmap::Vector{T} - properties::Dict{Compat.ASCIIString,Any} -end -ImageCmap{_,N}(data::AbstractArray{_,N}, cmap::AbstractVector, props::Dict) = ImageCmap{eltype(cmap),N,typeof(data)}(data, cmap, props) -ImageCmap(data::AbstractArray, cmap::AbstractVector; kwargs...) = ImageCmap(data, cmap, kwargs2dict(kwargs)) - -# Convenience constructors -""" -``` -img = grayim(A) -``` -creates a 2d or 3d _spatial_ grayscale Image from an AbstractArray -`A`, assumed to be in "horizontal-major" order (and without permuting -any dimensions). If you are working with 3d grayscale images, usage of -this function is strongly recommended. This can fix errors like any of -the following: - -``` -ERROR: Wrong number of spatial dimensions for plain Array, use an AbstractImage type -ERROR: Cannot infer colorspace of Array, use an AbstractImage type -ERROR: Cannot infer pixelspacing of Array, use an AbstractImage type -``` - -The main reason for such errors---and the reason that `grayim` is -recommended---is the Matlab-derived convention that a `m x n x 3` array is to be -interpreted as RGB. One might then say that an `m x n x k` array, for `k` -different from 3, could be interpreted as grayscale. However, this would lead to -difficult-to-track-down surprises on the day where `k` happened to be 3 for your -grayscale image. - -See also: `colorim`, `Image`, `convert(Image, A)`. -""" -grayim(A::AbstractImage) = A -grayim(A::AbstractArray{UInt8,2}) = grayim(reinterpret(UFixed8, A)) -grayim(A::AbstractArray{UInt16,2}) = grayim(reinterpret(UFixed16, A)) -grayim(A::AbstractArray{UInt8,3}) = grayim(reinterpret(UFixed8, A)) -grayim(A::AbstractArray{UInt16,3}) = grayim(reinterpret(UFixed16, A)) -grayim{T}(A::AbstractArray{T,2}) = Image(A; colorspace="Gray", spatialorder=["x","y"]) -grayim{T}(A::AbstractArray{T,3}) = Image(A; colorspace="Gray", spatialorder=["x","y","z"]) - -""" -``` -img = colorim(A, [colorspace]) -``` -Creates a 2d color image from an AbstractArray `A`, auto-detecting which of the -first or last dimension encodes the color and choosing between "horizontal-" and -"vertical-major" accordingly. `colorspace` defaults to `"RGB"` but could also be -e.g. `"Lab"` or `"HSV"`. If the array represents a 4-channel image, the -`colorspace` option is mandatory since there is no way to automatically -distinguish between `"ARGB"` and `"RGBA"`. If both the first and last -dimensions happen to be of size 3 or 4, it is impossible to guess which one -represents color and thus an error is generated. Thus, if your code needs to be -robust to arbitrary-sized images, you should use the `Image` constructor -directly. - -See also: `grayim`, `Image`, `convert(Image{RGB}, A)`. -""" -colorim(A::AbstractImage) = A -function colorim{T}(A::AbstractArray{T,3}) - if size(A, 1) == 4 || size(A, 3) == 4 - error("The array looks like a 4-channel color image. Please specify the colorspace explicitly (e.g. \"ARGB\" or \"RGBA\".)") - end - - colorim(A, "RGB") -end - -function colorim{T}(A::AbstractArray{T,3}, colorspace) - if 3 <= size(A, 1) <= 4 && 3 <= size(A, 3) <= 4 - error("Both first and last dimensions are of size 3 or 4; impossible to guess which is for color. Use the Image constructor directly.") - elseif 3 <= size(A, 1) <= 4 # Image as returned by imread for regular 2D RGB images - if T<:Fractional - CT = getcolortype(colorspace, eltype(A)) - Image(reinterpret(CT, A); spatialorder=["x","y"]) - else - Image(A; colorspace=colorspace, colordim=1, spatialorder=["x","y"]) - end - elseif 3 <= size(A, 3) <= 4 # "Matlab"-style image, as returned by convert(Array, im). - Image(A; colorspace=colorspace, colordim=3, spatialorder=["y","x"]) - else - error("Neither the first nor the last dimension is of size 3. This doesn't look like an RGB image.") - end -end - -colorim(A::AbstractArray{UInt8,3}) = colorim(reinterpret(UFixed8, A)) -colorim(A::AbstractArray{UInt16,3}) = colorim(reinterpret(UFixed16, A)) -colorim(A::AbstractArray{UInt8,3}, colorspace) = colorim(reinterpret(UFixed8, A), colorspace) -colorim(A::AbstractArray{UInt16,3}, colorspace) = colorim(reinterpret(UFixed16, A), colorspace) - - -#### Core operations #### - -eltype{T}(img::AbstractImage{T}) = T - -size(img::AbstractImage) = size(img.data) -size(img::AbstractImage, i::Integer) = size(img.data, i) -size(img::AbstractImage, dimname::AbstractString) = size(img.data, dimindex(img, dimname)) - -resize!(a::AbstractImage, nl::Integer) = resize!(a.data, nl) - -ndims(img::AbstractImage) = ndims(img.data) - -linearindexing(img::Image) = linearindexing(img.data) - -strides(img::AbstractImage) = strides(img.data) - -copy(img::Image) = Image(copy(img.data), dictcopy(img.properties)) -copy(img::ImageCmap) = ImageCmap(copy(img.data), copy(img.cmap), dictcopy(img.properties)) - -if VERSION < v"0.5.0-dev" - function dictcopy(dct) - newkeys = [copy(key) for key in keys(dct)] - newvals = [copy(val) for val in values(dct)] - Dict{Compat.ASCIIString,Any}(zip(newkeys,newvals)) - end -else - dictcopy(dct) = deepcopy(dct) -end - -""" -``` -imgnew = copyproperties(img, data) -``` -Creates a new image from the data array `data`, copying the properties from -Image `img`. -""" -copyproperties(img::AbstractArray, data::AbstractArray) = data - -copyproperties(img::AbstractImageDirect, data::AbstractArray) = Image(data, deepcopy(img.properties)) - -copyproperties(img::AbstractImageIndexed, data::AbstractArray) = ImageCmap(data, copy(img.cmap), deepcopy(img.properties)) - -copyproperties(img::AbstractImageDirect, _data::AbstractImageDirect) = copyproperties(img, data(_data)) - -""" -``` -imgnew = shareproperties(img, data) -``` -Creates a new image from the data array `data`, *sharing* the properties of -Image `img`. Any modifications made to the properties of one will affect the -other. -""" -shareproperties(img::AbstractArray, data::AbstractArray) = data - -shareproperties(img::AbstractImageDirect, data::AbstractArray) = Image(data, img.properties) - -shareproperties(img::AbstractImageIndexed, data::AbstractArray) = ImageCmap(data, img.cmap, img.properties) - -# similar -similar(img::AbstractImageDirect) = Image(similar(img.data), copy(img.properties)) -similar(img::AbstractImageDirect, ::NTuple{0}) = Image(similar(img.data), copy(img.properties)) - -similar(img::AbstractImageDirect, dims::Dims) = Image(similar(img.data, dims), copy(img.properties)) - -similar{T}(img::AbstractImageDirect, ::Type{T}) = Image(similar(img.data, T), copy(img.properties)) - -similar{T}(img::AbstractImageDirect, ::Type{T}, dims::Dims) = Image(similar(img.data, T, dims), copy(img.properties)) - -similar(img::AbstractImageIndexed) = ImageCmap(similar(img.data), copy(img.cmap), copy(img.properties)) -similar(img::AbstractImageIndexed, ::NTuple{0}) = ImageCmap(similar(img.data), copy(img.cmap), copy(img.properties)) - -similar(img::AbstractImageIndexed, dims::Dims) = ImageCmap(similar(img.data, dims), copy(img.cmap), copy(img.properties)) - -similar{T}(img::AbstractImageIndexed, ::Type{T}) = ImageCmap(similar(img.data, T), copy(img.cmap), copy(img.properties)) - -similar{T}(img::AbstractImageIndexed, ::Type{T}, dims::Dims) = ImageCmap(similar(img.data, T, dims), copy(img.cmap), copy(img.properties)) - -# copy properties -function copy!(imgdest::AbstractImage, imgsrc::AbstractImage, prop1::String, props::String...) - imgdest[prop1] = imgsrc[prop1] - for p in props - imgdest[p] = imgsrc[p] - end - imgdest -end - -function reshape{N}(img::AbstractImage, dims::NTuple{N,Int}) - ret = copyproperties(img, reshape(data(img), dims)) - for prop in spatialproperties(img) - delete!(ret, prop) - end - if colordim(img) != 0 - delete!(ret, "colordim") - delete!(ret, "colorspace") - end - delete!(ret, "timedim") - ret -end - -## reinterpret: Color->T -# Arrays -reinterpret{CV1<:Colorant,CV2<:Colorant}(::Type{CV1}, A::Array{CV2,1}) = _reinterpret_cvarray(CV1, A) -reinterpret{CV1<:Colorant,CV2<:Colorant}(::Type{CV1}, A::Array{CV2}) = _reinterpret_cvarray(CV1, A) -reinterpret{T,CV<:Colorant}(::Type{T}, A::Array{CV,1}) = _reinterpret_cvarray(T, A) -reinterpret{T,CV<:Colorant}(::Type{T}, A::Array{CV}) = _reinterpret_cvarray(T, A) -reinterpret{T,CV<:Colorant}(::Type{T}, A::StridedArray{CV}) = view(_reinterpret_cvarray(T, A.parent), A.indexes...) -function _reinterpret_cvarray{T,CV<:Colorant}(::Type{T}, A::Array{CV}) - if sizeof(T) == sizeof(CV) - return reinterpret(T, A, size(A)) - elseif sizeof(T)*n_elts(CV) == sizeof(CV) - return reinterpret(T, A, tuple(n_elts(CV), size(A)...)) - end - error("result shape not specified") -end -reinterpret{CV<:Colorant}(A::StridedArray{CV}) = reinterpret(eltype(CV), A) - -# Images -reinterpret{CV1<:Colorant,CV2<:Colorant}(::Type{CV1}, img::AbstractImageDirect{CV2}) = - shareproperties(img, reinterpret(CV1, data(img))) -function reinterpret{CV<:Colorant}(::Type{UInt32}, img::AbstractImageDirect{CV}) - CV <: Union{RGB24, ARGB32} || (CV <: AbstractRGB && sizeof(CV) == 4) || error("Can't convert $CV to UInt32") - A = reinterpret(UInt32, data(img)) - props = copy(properties(img)) - props["colorspace"] = colorspace(img) - Image(A, props) -end -function reinterpret{T,CV2<:Colorant}(::Type{T}, img::AbstractImageDirect{CV2}) - A = reinterpret(T, data(img)) - props = copy(properties(img)) - props["colorspace"] = colorspace(img) - if ndims(A) > ndims(img) - props["colordim"] = 1 - end - Image(A, props) -end - -## reinterpret: T->Color -# We have to distinguish two forms of call: -# form 1: reinterpret(RGB, img) -# form 2: reinterpret(RGB{UFixed8}, img) -# Arrays -reinterpret{T,CV<:Colorant}(::Type{CV}, A::Array{T,1}) = _reinterpret(CV, eltype(CV), A) -reinterpret{T,CV<:Colorant}(::Type{CV}, A::Array{T}) = _reinterpret(CV, eltype(CV), A) -_reinterpret{T,CV<:Colorant}(::Type{CV}, ::Type{Any}, A::Array{T}) = - _reinterpret_array_cv(CV{T}, A) # form 1 (turn into a form 2 call by filling in the element type of the array) -_reinterpret{T,CV<:Colorant}(::Type{CV}, TT::DataType, A::Array{T}) = - _reinterpret_array_cv(CV, A) # form 2 -function _reinterpret_array_cv{T,CV<:Colorant}(::Type{CV}, A::Array{T}) - if sizeof(T) == sizeof(CV) - return reinterpret(CV, A, size(A)) - elseif sizeof(T)*size(A,1) == sizeof(CV) - return reinterpret(CV, A, size(A)[2:end]) - end - error("result shape not specified") -end -# This version is used by the deserializer to convert UInt8 buffers back to their original type. Fixes #287. -_reinterpret_array_cv{CV<:Colorant}(::Type{CV}, A::Vector{UInt8}) = - reinterpret(CV, A, (div(length(A), sizeof(CV)),)) - -# Images -function reinterpret{T,CV<:Colorant}(::Type{CV}, img::AbstractImageDirect{T}) - A = reinterpret(CV, data(img)) - props = copy(properties(img)) - haskey(props, "colorspace") && delete!(props, "colorspace") - haskey(props, "colordim") && delete!(props, "colordim") - Image(A, props) -end - -# T->S -function reinterpret{T,S}(::Type{T}, img::AbstractImageDirect{S}) - if sizeof(S) != sizeof(T) - error("result shape not specified") - end - shareproperties(img, reinterpret(T, data(img))) -end - -""" -``` -imgraw = raw(img) -``` -returns a reference to the array data in raw (machine-native) storage -format. This is particularly useful when Images.jl wraps image data in -a `FixedPointNumbers` type, and raw data access is desired. For -example - -``` -img = load("someimage.tif") -typeof( data(img) ) # return Array{UFixed{UInt8,8},2} -typeof( raw(img) ) # returns Array{UInt8,2} -``` -""" -raw(img::AbstractArray) = _raw(data(img), eltype(eltype(img))) -_raw{T<:UFixed}(A::Array, ::Type{T}) = reinterpret(FixedPointNumbers.rawtype(T), A) -_raw{T}(A::Array, ::Type{T}) = A -_raw{T}(A::AbstractArray, ::Type{T}) = _raw(convert(Array, A), T) - -## convert -# Implementations safe for under-specified color types -# ambiguity resolution: -convert{T<:Colorant,n}(::Type{Array{T}}, x::Array{T,n}) = x -convert{T<:Colorant,n}(::Type{Array{T,n}}, x::Array{T,n}) = x -convert{T<:Colorant,n}(::Type{Array{T}}, x::BitArray{n}) = convert(Array{ccolor(T,Gray{Bool}),n}, x) -if VERSION >= v"0.5.0-dev" - # See julia #15801. May be unfixable on 0.4. - convert{T<:Colorant,n}(::Type{Array{T,n}}, x::BitArray{n}) = Base._convert(Array{ccolor(T,Gray{Bool}),n}, x) -end - -if VERSION < v"0.5.0-dev" - convert{T<:Colorant,n,S}(::Type{Array{T}}, x::Array{S,n}) = convert(Array{ccolor(T,S),n}, x) - convert{T<:Colorant,n,S}(::Type{Array{T,n}}, x::Array{S,n}) = copy!(Array(ccolor(T,S), size(x)), x) -end - -""" -``` -img = convert(Image, A) -img = convert(Image{HSV}, img) -``` -Create a 2d Image from an array, setting up default properties. The -data array is assumed to be in "vertical-major" order, and an m-by-n-by-3 array -will be assumed to encode color along its third dimension. - -Optionally, you can specify the desired colorspace of the returned `img`. - -See also: `Image`, `grayim`, `colorim`. -""" -convert{T}(::Type{Image{T}}, img::Image{T}) = img -convert(::Type{Image}, img::Image) = img -convert(::Type{Image}, A::AbstractArray) = Image(A, properties(A)) -# Convert an indexed image (cmap) to a direct image -function convert(::Type{Image}, img::ImageCmap) - data = reshape(img.cmap[vec(img.data)], size(img.data)) - Image(data, copy(properties(img))) -end - -convert{C<:Colorant}(::Type{Image{C}}, img::Image{C}) = img -convert{Cdest<:Colorant,Csrc<:Colorant}(::Type{Image{Cdest}}, img::Image{Csrc}) = deletecs!(copyproperties(img, convert(Array{ccolor(Cdest,Csrc)}, data(img)))) -convert{Cdest<:Colorant,Csrc<:Colorant}(::Type{Image{Cdest}}, img::AbstractArray{Csrc}) = deletecs!(Image(convert(Array{Cdest}, data(img)), properties(img))) -deletecs!(img) = (delete!(img, "colorspace"); img) - -# Convert an Image to an array. We convert the image into the canonical storage order convention for arrays. -# We restrict this to 2d images because for plain arrays this convention exists only for 2d. -# In other cases---or if you don't want the storage order altered---just use data(img) -""" -`A = convert(Array, img)` converts an Image `img` to an Array, -permuting dimensions (if needed) to put it in vertical-major (Matlab) -storage order. - -See also `data`. -""" -convert{T<:Real,N}(::Type{Array{T}}, img::AbstractImageDirect{T,N}) = convert(Array{T,N}, img) -convert{T<:Colorant,N}(::Type{Array{T}}, img::AbstractImageDirect{T,N}) = convert(Array{T,N}, img) -convert{T}(::Type{Vector{T}}, img::AbstractImageDirect{T,1}) = convert(Vector{T}, data(img)) -convert{T<:Colorant,N,S}(::Type{Array{T,N}}, img::AbstractImageDirect{S,N}) = _convert(Array{ccolor(T,S),N}, img) -convert{T,N,S}(::Type{Array{T,N}}, img::AbstractImageDirect{S,N}) = _convert(Array{T,N}, img) -function _convert{T,N,S}(::Type{Array{T,N}}, img::AbstractImageDirect{S,N}) - assert2d(img) # only well-defined in 2d - p = permutation_canonical(img) - dat = convert(Array{T}, data(img)) - if issorted(p) - return dat - else - return permutedims(dat, p) - end -end -convert{T<:Colorant,n,S}(::Type{Array{T}}, x::AbstractArray{S,n}) = convert(Array{ccolor(T,S),n}, x) -if VERSION < v"0.5.0-dev" - convert{T,N}(::Type{Array{T,N}}, img::AbstractArray) = copy!(Array{T}(size(img)), img) -else - convert{T<:Colorant,n,S}(::Type{Array{T,n}}, x::AbstractArray{S,n}) = copy!(Array(ccolor(T,S), size(x)), x) -end - -convert{Cdest<:Colorant,Csrc<:Colorant}(::Type{Image{Cdest}}, img::AbstractImageDirect{Csrc}) = - copyproperties(img, convert(Array{ccolor(Cdest,Csrc)}, data(img))) # FIXME when Julia issue ?? is fixed -convert{Cdest<:Colorant,Csrc<:Colorant,N}(::Type{Array{Cdest}}, img::AbstractImageDirect{Csrc,N}) = - convert(Array{ccolor(Cdest,Csrc)}, convert(Array{Csrc,N}, img)) - -#convert{T<:Colorant}(::Type{Array{T}}, x) = copy!(similar(x,ccolor(T,eltype(x))), x) -#convert{T<:Colorant,n,S}(::Type{Array{T,n}}, x::AbstractArray{S,n}) = copy!(Array(ccolor(T,S), size(x)), x) - -""" -`imgs = separate(img)` separates the color channels of `img`, for -example returning an `m-by-n-by-3` array from an `m-by-n` array of -`RGB`. -""" -function separate{CV<:Colorant}(img::AbstractImage{CV}) - p = permutation_canonical(img) - A = _separate(data(img), p) - so = spatialorder(img)[p] - props = copy(properties(img)) - props["colorspace"] = colorspace(img) - props["colordim"] = ndims(A) - props["spatialorder"] = so - Image(A, props) -end -function _separate{CV}(A::Array{CV}, p) - T = eltype(CV) - if n_elts(CV) > 1 - permutedims(reinterpret(T, A, tuple(n_elts(CV), size(A)...)), [p+1;1]) - else - permutedims(reinterpret(T, A, size(A)), p) - end -end -_separate{CV}(A::AbstractArray{CV}, p) = _separate(convert(Array, A), p) -function separate{CV<:Colorant}(A::Array{CV}) - T = eltype(CV) - if n_elts(CV) > 1 - permutedims(reinterpret(T, A, tuple(n_elts(CV), size(A)...)), [2:ndims(A)+1;1]) - else - reinterpret(T, A, size(A)) - end -end -separate{CV<:Colorant}(A::AbstractArray{CV}) = separate(convert(Array, A)) -separate(A::AbstractArray) = A - -# Image{Numbers} -> Image{Colorant} (the opposite of separate) -convert{C<:Colorant,T<:Fractional}(::Type{Image{C}}, img::Union{AbstractArray{T},AbstractImageDirect{T}}) = - _convert(Image{C}, eltype(C), img) -_convert{C<:Colorant,T<:Fractional}(::Type{Image{C}}, ::Type{Any}, img::Union{AbstractArray{T},AbstractImageDirect{T}}) = - _convert(Image{C{T}}, img) -_convert{C<:Colorant,T<:Fractional}(::Type{Image{C}}, ::DataType, img::Union{AbstractArray{T},AbstractImageDirect{T}}) = - _convert(Image{C}, img) -function _convert{C<:Colorant,T<:Fractional}(::Type{Image{C}}, img::Union{AbstractArray{T},AbstractImageDirect{T}}) - cd = colordim(img) - if cd > 0 - p = [cd; setdiff(1:ndims(img), cd)] - A = permutedims(data(img), p) - else - A = data(img) - end - CV = getcolortype(colorspace(img), T) - ACV = convert(Array{C}, reinterpret(CV, A)) - props = copy(properties(img)) - haskey(props, "colordim") && delete!(props, "colordim") - haskey(props, "colorspace") && delete!(props, "colorspace") - Image(ACV, props) -end - - -# Indexing. In addition to conventional array indexing, support syntax like -# img["x", 100:400, "t", 32] -# where anything not mentioned by name is taken to include the whole range - -typealias RealIndex{T<:Real} Union{T, AbstractVector{T}, Colon} - -# setindex! -setindex!(img::AbstractImage, X, i::Real) = setindex!(img.data, X, i) -setindex!(img::AbstractImage, X, I::RealIndex) = setindex!(img.data, X, I) -setindex!(img::AbstractImage, X, I::RealIndex, J::RealIndex) = setindex!(img.data, X, I, J) -setindex!(img::AbstractImage, X, I::RealIndex, J::RealIndex, K::RealIndex) = setindex!(img.data, X, I, J, K) -setindex!(img::AbstractImage, X, I::RealIndex, J::RealIndex, - K::RealIndex, L::RealIndex) = setindex!(img.data, X, I, J, K, L) -setindex!(img::AbstractImage, X, I::RealIndex...) = setindex!(img.data, X, I...) -setindex!(img::AbstractImage, X, dimname::AbstractString, ind::RealIndex, nameind...) = setindex!(img.data, X, coords(img, dimname, ind, nameind...)...) - -# Adding a new property via setindex! -setindex!(img::AbstractImage, X, propname::AbstractString) = setindex!(img.properties, X, propname) - -# Delete a property! -delete!(img::AbstractImage, propname::AbstractString) = delete!(img.properties, propname) - - -# getindex, sub, and slice return a value or AbstractArray, not an Image -getindex(img::AbstractImage, i::Real) = getindex(img.data, i) -getindex(img::AbstractImage, I::RealIndex) = getindex(img.data, I) -getindex(img::AbstractImage, I::RealIndex, J::RealIndex) = getindex(img.data, I, J) -getindex(img::AbstractImage, I::RealIndex, J::RealIndex, K::RealIndex) = getindex(img.data, I, J, K) -getindex(img::AbstractImage, I::RealIndex, J::RealIndex, - K::RealIndex, L::RealIndex) = getindex(img.data, I, J, K, L) -getindex(img::AbstractImage, I::RealIndex...) = getindex(img.data, I...) - -# getindex(img::AbstractImage, dimname::AbstractString, ind::RealIndex, nameind...) = getindex(img.data, coords(img, dimname, ind, nameind...)...) -getindex(img::AbstractImage, dimname::String, ind, nameind...) = getindex(img.data, coords(img, dimname, ind, nameind...)...) - -getindex(img::AbstractImage, propname::String) = getindex(img.properties, propname) - -typealias Indexable{T<:Real} Union{Int, AbstractVector{T}, Colon} # for ambiguity resolution -view(img::AbstractImage, I::Indexable...) = view(img.data, I...) -view(img::AbstractImage, I::RealIndex...) = view(img.data, I...) - -view(img::AbstractImage, dimname::String, ind::RealIndex, nameind...) = view(img.data, coords(img, dimname, ind, nameind...)...) - -""" -``` -imgnew = getindexim(img, i, j, k,...) -imgnew = getindexim(img, "x", 100:200, "y", 400:600) -``` -return a new Image `imgnew`, copying (and where necessary modifying) -the properties of `img`. This is in contrast with `img[i, j, k...]`, -which returns an `Array`. -""" -function getindexim(img::AbstractImage, I::RealIndex...) - ret = copyproperties(img, data(img)[I...]) - cd = colordim(img) - nd = ndims(ret) - if cd > nd || (cd > 0 && _length(I[cd], img, cd) < size(img, cd)) - ret["colorspace"] = "Unknown" - if cd > nd - ret["colordim"] = 0 - end - end - td = timedim(img) - if td > nd - ret["timedim"] = 0 - end - sp = spatialproperties(img) - for p in sp - val = ret[p] - if length(val) > nd - ret[p] = val[1:nd] - end - end - ret -end -_length(indx, A, d) = length(indx) -_length(indx::Colon, A, d) = size(A,d) - - -getindexim(img::AbstractImage, dimname::String, ind::RealIndex, nameind...) = getindexim(img, coords(img, dimname, ind, nameind...)...) - -""" -``` -imgs = subim(img, i, j, k, ...) -imgs = subim(img, "x", 100:200, "y", 400:600) -``` -returns an `Image` with `SubArray` data, with indexing semantics similar to `sub`. -""" -subim(img::AbstractImage, I::RealIndex...) = _subim(img, I) -_subim{TT}(img, I::TT) = shareproperties(img, viewsub(img.data, I...)) # work around #8504 - -@inline viewsub(A::AbstractArray, I...) = view(A, _viewsub(I...)...) -_viewsub() = () -_viewsub(i, I::RealIndex...) = (i, _viewsub(I...)...) -_viewsub(i::Real, I::RealIndex...) = (i:i, _viewsub(I...)...) -_viewsub(I::Real...) = I - -subim(img::AbstractImage, dimname::String, ind::RealIndex, nameind...) = subim(img, coords(img, dimname, ind, nameind...)...) - -""" -``` -imgs = sliceim(img, i, j, k, ...) -imgs = sliceim(img, "x", 100:200, "y", 400:600) -``` -returns an `Image` with `SubArray` data, with indexing semantics similar to `slice`. -""" -sliceim(img::AbstractImage, I::RealIndex...) = _sliceim(img, I) -function _sliceim{IT}(img::AbstractImage, I::IT) - dimmap = Array(Int, ndims(img)) - n = 0 - for j = 1:ndims(img) - if !isa(I[j], Real); n += 1; end; - dimmap[j] = n - end - S = view(img.data, I...) - ret = copyproperties(img, S) - cd = colordim(img) - if cd > 0 - if isa(I[cd], Real) - ret.properties["colordim"] = 0 - ret.properties["colorspace"] = "Unknown" - else - ret.properties["colordim"] = dimmap[cd] - if I[cd] != 1:size(img, cd) - ret.properties["colorspace"] = "Unknown" - end - end - end - td = timedim(img) - if td > 0 - ret.properties["timedim"] = isa(I[td], Real) ? 0 : dimmap[td] - end - sp = spatialproperties(img) - if !isempty(sp) - c = coords_spatial(img) - keep = Bool[map(x -> !isa(x, Real), I[c])...] - if !all(keep) - for pname in sp - p = img.properties[pname] - if isa(p, Vector) - ret.properties[pname] = p[keep] - elseif isa(p, Matrix) - ret.properties[pname] = p[keep, keep] - else - error("Do not know how to handle property ", pname) - end - end - end - end - ret -end - -sliceim(img::AbstractImage, dimname::AbstractString, ind::RealIndex, nameind...) = sliceim(img, coords(img, dimname, ind, nameind...)...) - - -# Iteration -# Defer to the array object in case it has special iteration defined -next{T,N}(img::AbstractImage{T,N}, s::Tuple{Bool,Base.IteratorsMD.CartesianIndex{N}}) = next(data(img), s) -done{T,N}(img::AbstractImage{T,N}, s::Tuple{Bool,Base.IteratorsMD.CartesianIndex{N}}) = done(data(img), s) -start(img::AbstractImage) = start(data(img)) -next(img::AbstractImage, s) = next(data(img), s) -done(img::AbstractImage, s) = done(data(img), s) - - -# We'll frequently want to pull out different 2d slices from the same image, so here's a type and set of functions making that easier. -# We deliberately do not require the user to specify the full list of new slicing/ranging parameters, as often we'll want to change some aspects (e.g., z-slice) but not others (e.g., color coordinates) -type SliceData - slicedims::Tuple{Vararg{Int}} - slicestrides::Tuple{Vararg{Int}} - rangedims::Tuple{Vararg{Int}} - - function SliceData(A::AbstractArray, slicedims::Int...) - keep = trues(ndims(A)) - for i = 1:length(slicedims) - keep[slicedims[i]] = false - end - s = strides(A) - new(slicedims, ntuple(i->s[slicedims[i]], length(slicedims)), tuple((1:ndims(A))[keep]...)) - end -end - -SliceData(img::AbstractImage, dimname::AbstractString, dimnames::AbstractString...) = SliceData(img, dimindexes(img, dimname, dimnames...)...) - -function _slice(A::AbstractArray, sd::SliceData, I::Int...) - if length(I) != length(sd.slicedims) - throw(BoundsError()) - end - indexes = RangeIndex[1:size(A, i) for i = 1:ndims(A)] - for i = 1:length(I) - indexes[sd.slicedims[i]] = I[i] - end - indexes -end - -function slice(A::AbstractArray, sd::SliceData, I::Int...) - indexes = _slice(A, sd, I...) - slice(A, indexes...) -end - -function sliceim(img::AbstractImage, sd::SliceData, I::Int...) - indexes = _slice(img, sd, I...) - sliceim(img, indexes...) -end - -function first_index(A::SubArray, sd::SliceData) - newfirst = 1 - for i = 1:length(sd.slicedims) - newfirst += (A.indexes[sd.slicedims[i]]-1)*sd.slicestrides[i] - end - strds = strides(A) - for i = 1:length(sd.rangedims) - newfirst += (A.indexes[sd.rangedims[i]][1]-1)*strds[i] - end - newfirst -end - -function reslice!(A::SubArray, sd::SliceData, I::Int...) - indexes = RangeIndex[A.indexes...] - for i = 1:length(I) - indexes[sd.slicedims[i]] = I[i] - end - A.indexes = tuple(indexes...) - A.first_index = first_index(A, sd) - A -end - -function reslice!(img::AbstractImage, sd::SliceData, I::Int...) - reslice!(img.data, sd, I...) - img -end - -function rerange!(A::SubArray, sd::SliceData, I::Tuple{Vararg{RangeIndex}}) - indexes = RangeIndex[A.indexes...] - for i = 1:length(I) - indexes[sd.rangedims[i]] = I[i] - end - A.indexes = tuple(indexes...) - A.first_index = first_index(A, sd) - A -end - -function rerange!(img::AbstractImage, sd::SliceData, I::Tuple{Vararg{RangeIndex}}) - rerange!(img.data, sd, I...) - img -end - - -const emptyset = Set() -function showim(io::IO, img::AbstractImageDirect) - IT = typeof(img) - print(io, colorspace(img), " ", IT.name, " with:\n data: ", summary(img.data), "\n properties:") - showdictlines(io, img.properties, get(img, "suppress", emptyset)) -end -function showim(io::IO, img::AbstractImageIndexed) - IT = typeof(img) - print(io, colorspace(img), " ", IT.name, " with:\n data: ", summary(img.data), "\n cmap: ", summary(img.cmap), "\n properties:") - showdictlines(io, img.properties, get(img, "suppress", emptyset)) -end -show(io::IO, img::AbstractImageDirect) = showim(io, img) -@compat Base.show(io::IO, ::MIME"text/plain", img::AbstractImageDirect) = showim(io, img) -show(io::IO, img::AbstractImageIndexed) = showim(io, img) -@compat Base.show(io::IO, ::MIME"text/plain", img::AbstractImageIndexed) = showim(io, img) - -""" -``` -A = data(img) -``` -returns a reference `A` to the array data in `img`. It allows you to -use algorithms specialized for particular `AbstractArray` types on -`Image` types. This works for both `AbstractImage`s and -`AbstractArray`s (for the latter it just returns the input), so is a -"safe" component of any algorithm. - -For algorithms written to accept arbitrary `AbstractArrays`, this -function is not needed. -""" -data(img::AbstractArray) = img -data(img::AbstractImage) = img.data - -minimum(img::AbstractImageDirect) = minimum(img.data) -maximum(img::AbstractImageDirect) = maximum(img.data) -# min/max deliberately not defined for AbstractImageIndexed - -function _squeeze(img::AbstractImage, dims) - imgret = copyproperties(img, squeeze(data(img), dims)) - td = timedim(img) - if td > 0 - imgret["timedim"] = squeezedims(td, dims) - end - cd = colordim(img) - if cd > 0 - imgret["colordim"] = squeezedims(cd, dims) - end - c = coords_spatial(img) - keep = setdiff(c, dims) - if length(keep) < length(c) - sp = spatialproperties(img) - if !isempty(sp) - for pname in sp - p = img.properties[pname] - if isa(p, Vector) - imgret.properties[pname] = p[keep] - elseif isa(p, Matrix) - imgret.properties[pname] = p[keep, keep] - else - error("Do not know how to handle property ", pname) - end - end - end - end - imgret -end -squeeze(img::AbstractImage, dims::Integer) = _squeeze(img, dims) -squeeze(img::AbstractImage, dims::Dims) = _squeeze(img, dims) -squeeze(img::AbstractImage, dims) = _squeeze(img, dims) - -function squeezedims(val, dims) - if in(val, dims) - val = 0 - else - dec = 0 - for d in dims - dec += val > d - end - val -= dec - end - val -end - -#### Properties #### - -# Generic programming with images uses properties to obtain information. The strategy is to define a particular property name, and then write an accessor function of the same name. The accessor function provides default behavior for plain arrays and when the property is not defined. Alternatively, use get(img, "propname", default) or haskey(img, "propname") to define your own default behavior. - -# You can define whatever properties you want. Here is a list of properties used -# in some algorithms: -# colorspace: "RGB", "ARGB", "Gray", "Binary", "RGB24", "Lab", "HSV", etc. -# colordim: the array dimension used to store color information, or 0 if there -# is no dimension corresponding to color -# timedim: the array dimension used for time (i.e., sequence), or 0 for single images -# pixelspacing: the spacing between adjacent pixels along spatial dimensions -# spacedirections: the direction of each array axis in physical space (a vector-of-vectors, one per dimension) -# spatialorder: a string naming each spatial dimension, in the storage order of -# the data array. Names can be arbitrary, but the choices "x" and "y" have special -# meaning (horizontal and vertical, respectively, irrespective of storage order). -# If supplied, you must have one entry per spatial dimension. - -""" -`prop = properties(img)` returns the properties-dictionary for an -`AbstractImage`, or creates one if `img` is an `AbstractArray`. -""" -properties(A::AbstractArray) = Dict( - "colorspace" => colorspace(A), - "colordim" => colordim(A), - "timedim" => timedim(A), - "pixelspacing" => pixelspacing(A), - "spatialorder" => spatialorder(A)) -properties{C<:Colorant}(A::AbstractArray{C}) = Dict( - "timedim" => timedim(A), - "pixelspacing" => pixelspacing(A), - "spatialorder" => spatialorder(A)) -properties(img::AbstractImage) = img.properties - -haskey(a::AbstractArray, k::AbstractString) = false -haskey(img::AbstractImage, k::AbstractString) = haskey(img.properties, k) - -get(img::AbstractArray, k::AbstractString, default) = default -get(img::AbstractImage, k::AbstractString, default) = get(img.properties, k, default) - -# So that defaults don't have to be evaluated unless they are needed, we also define a @get macro (thanks Toivo Hennington): -macro get(img, k, default) - quote - img, k = $(esc(img)), $(esc(k)) - local val - if !isa(img, AbstractImage) - val = $(esc(default)) - else - index = Base.ht_keyindex(img.properties, k) - val = (index > 0) ? img.properties.vals[index] : $(esc(default)) - end - val - end -end - -# Using plain arrays, we have to make all sorts of guesses about colorspace and storage order. This can be a big problem for three-dimensional images, image sequences, cameras with more than 16-bits, etc. In such cases use an AbstractImage type. - -# Here are the two most important assumptions (see also colorspace below): -defaultarraycolordim = 3 -# defaults for plain arrays ("vertical-major") -const yx = ["y", "x"] -# order used in Cairo & most image file formats (with color as the very first dimension) -const xy = ["x", "y"] - -""" -``` -order = spatialorder(img) -order = spatialorder(ImageType) -``` - -Returns the storage order of the _spatial_ coordinates of the image, e.g., -`["y", "x"]`. The second version works on a type, e.g., `Matrix`. See -`storageorder`, `timedim`, and `colordim` for related properties. -""" -spatialorder(::Type{Matrix}) = yx -spatialorder(img::AbstractArray) = (sdims(img) == 2) ? spatialorder(Matrix) : error("Wrong number of spatial dimensions for plain Array, use an AbstractImage type") - -""" -`isdirect(img)` returns true if `img` encodes its values directly, -rather than via an indexed colormap. -""" -isdirect(img::AbstractArray) = true -isdirect(img::AbstractImageDirect) = true -isdirect(img::AbstractImageIndexed) = false - -""" -`cs = colorspace(img)` returns a string specifying the colorspace -representation of the image. -""" -colorspace{C<:Colorant}(img::AbstractVector{C}) = ColorTypes.colorant_string(C) -colorspace{C<:Colorant}(img::AbstractMatrix{C}) = ColorTypes.colorant_string(C) -colorspace{C<:Colorant}(img::AbstractArray{C,3}) = ColorTypes.colorant_string(C) -colorspace{C<:Colorant}(img::AbstractImage{C}) = ColorTypes.colorant_string(C) -colorspace(img::AbstractVector{Bool}) = "Binary" -colorspace(img::AbstractMatrix{Bool}) = "Binary" -colorspace(img::AbstractArray{Bool}) = "Binary" -colorspace(img::AbstractArray{Bool,3}) = "Binary" -colorspace(img::AbstractMatrix{UInt32}) = "RGB24" -colorspace(img::AbstractVector) = "Gray" -colorspace(img::AbstractMatrix) = "Gray" -colorspace{T}(img::AbstractArray{T,3}) = (size(img, defaultarraycolordim) == 3) ? "RGB" : error("Cannot infer colorspace of Array, use an AbstractImage type") -colorspace(img::AbstractImage{Bool}) = "Binary" -colorspace{T,N,A<:AbstractArray}(img::ImageCmap{T,N,A}) = string(T.name.name) -colorspace(img::AbstractImageIndexed) = @get img "colorspace" csinfer(eltype(img.cmap)) -colorspace{T}(img::AbstractImageIndexed{T,2}) = @get img "colorspace" csinfer(eltype(img.cmap)) -csinfer{C<:Colorant}(::Type{C}) = ColorTypes.colorant_string(C) -csinfer(C) = "Unknown" -colorspace(img::AbstractImage) = get(img.properties, "colorspace", "Unknown") - -colorspacedict = Dict{Compat.ASCIIString,Any}() -for ACV in (Color, AbstractRGB) - for CV in subtypes(ACV) - (length(CV.parameters) == 1 && !(CV.abstract)) || continue - str = string(CV.name.name) - colorspacedict[str] = CV - end -end -function getcolortype{T}(str::String, ::Type{T}) - if haskey(colorspacedict, str) - CV = colorspacedict[str] - return CV{T} - else - if endswith(str, "A") - CV = colorspacedict[str[1:end-1]] - return coloralpha(CV){T} - elseif startswith(str, "A") - CV = colorspacedict[str[2:end]] - return alphacolor(CV){T} - else - error("colorspace $str not recognized") - end - end -end - -""" -`dim = colordim(img)` returns the dimension used to encode color, or 0 -if no dimension of the array is used for color. For example, an -`Array` of size `(m, n, 3)` would result in 3, whereas an `Array` of -`RGB` colorvalues would yield 0. - -See also: `ncolorelem`, `timedim`. -""" -colordim{C<:Colorant}(img::AbstractVector{C}) = 0 -colordim{C<:Colorant}(img::AbstractMatrix{C}) = 0 -colordim{C<:Colorant}(img::AbstractArray{C,3}) = 0 -colordim{C<:Colorant}(img::AbstractImage{C}) = 0 -colordim(img::AbstractVector) = 0 -colordim(img::AbstractMatrix) = 0 -colordim{T}(img::AbstractImageDirect{T,3}) = get(img, "colordim", 0)::Int -colordim{T}(img::AbstractArray{T,3}) = (size(img, defaultarraycolordim) == 3) ? 3 : 0 -colordim(img::AbstractImageDirect) = get(img, "colordim", 0) -colordim(img::AbstractImageIndexed) = 0 - -""" -`dim = timedim(img)` returns the dimension used to represent time, or -0 if this is a single image. - -See also: `nimages`, `colordim`. -""" -timedim(img) = get(img, "timedim", 0)::Int - -oldlimits(img::AbstractArray{Bool}) = 0,1 -# limits{T<:Integer}(img::AbstractArray{T}) = typemin(T), typemax(T) # best not to use Integers... -oldlimits{T<:AbstractFloat}(img::AbstractArray{T}) = zero(T), one(T) -oldlimits(img::AbstractImage{Bool}) = 0,1 -oldlimits{T}(img::AbstractImageDirect{T}) = get(img, "limits", (zero(T), one(T))) -oldlimits(img::AbstractImageIndexed) = @get img "limits" (minimum(img.cmap), maximum(img.cmap)) - -""" -``` -ps = pixelspacing(img) -``` - -Returns a vector `ps` containing the spacing between adjacent pixels along each -dimension. If this property is not available, it will be computed from -`"spacedirections"` if present; otherwise it defaults to `ones(sdims(img))`. If -desired, you can set this property in terms of physical -[units](https://github.com/Keno/SIUnits.jl). - -See also: `spacedirections`. -""" -pixelspacing{T}(img::AbstractArray{T,3}) = (size(img, defaultarraycolordim) == 3) ? [1.0,1.0] : error("Cannot infer pixelspacing of Array, use an AbstractImage type") -pixelspacing(img::AbstractMatrix) = [1.0,1.0] -pixelspacing{T}(img::AbstractImage{T}) = @get img "pixelspacing" _pixelspacing(img) -function _pixelspacing(img::AbstractImage) - if haskey(img, "spacedirections") - sd = img["spacedirections"] - return [maximum(@compat abs.(sd[i])) for i = 1:length(sd)] - end - ones(sdims(img)) -end - - -""" -``` -sd = spacedirections(img) -``` - -Returns a vector-of-vectors `sd`, each `sd[i]`indicating the displacement between adjacent -pixels along spatial axis `i` of the image array, relative to some external -coordinate system ("physical coordinates"). For example, you could indicate -that a photograph was taken with the camera tilted 30-degree relative to -vertical using - -``` -img["spacedirections"] = [[0.866025,-0.5],[0.5,0.866025]] -``` - -If not specified, it will be computed from `pixelspacing(img)`, placing the -spacing along the "diagonal". If desired, you can set this property in terms of -physical [units](https://github.com/loladiro/SIUnits.jl). - -See also: `pixelspacing`. -""" -spacedirections(img::AbstractArray) = @get img "spacedirections" _spacedirections(img) -function _spacedirections(img::AbstractArray) - ps = pixelspacing(img) - T = eltype(ps) - nd = length(ps) - Vector{T}[(tmp = zeros(T, nd); tmp[i] = ps[i]; tmp) for i = 1:nd] -end - -""" -``` -so = spatialorder(img) -so = spatialorder(ImageType) -``` - -Returns the storage order of the *spatial* coordinates of the image, e.g., -`["y", "x"]`. The second version works on a type, e.g., `Matrix`. - -See also: `storageorder`, `coords_spatial`, `timedim`, and `colordim`. -""" -spatialorder(img::AbstractImage) = @get img "spatialorder" _spatialorder(img) -_spatialorder(img::AbstractImage) = (sdims(img) == 2) ? spatialorder(Matrix) : error("Cannot guess default spatial order for ", sdims(img), "-dimensional images") - -""" -``` -so = storageorder(img) -``` - -Returns the complete storage order of the image array, including `"t"` for time -and `"color"` for color. - -See also: `spatialorder`, `colordim`, `timedim`. -""" -function storageorder(img::AbstractArray) - so = Array(Compat.ASCIIString, ndims(img)) - so[coords_spatial(img)] = spatialorder(img) - cd = colordim(img) - if cd != 0 - so[cd] = "color" - end - td = timedim(img) - if td != 0 - so[td] = "t" - end - so -end - -# number of spatial dimensions in the image -""" -`n = sdims(img)` is similar to `ndims`, but it returns just the number of *spatial* dimensions in -the image array (excluding color and time). -""" -sdims(img) = ndims(img) - (colordim(img) != 0) - (timedim(img) != 0) - -# number of time slices -""" -`n = nimages(img)` returns the number of time-points in the image -array. This is safer than `size(img, "t")` because it also works for -plain `AbstractArray` types. -""" -function nimages(img) - sd = timedim(img) - if sd > 0 - return size(img, sd) - else - return 1 - end -end - -""" -`n = ncolorelem(img)` returns the number of color elements/voxel, or 1 if color is not a separate dimension of the array. -""" -function ncolorelem(img) - cd = colordim(img) - return cd > 0 ? size(img, cd) : 1 -end - -""" -`c = coords_spatial(img)` returns a vector listing the spatial -dimensions of the image. For example, an `Array` of size `(m,n,3)` -would return `[1,2]`. - -See also: `spatialorder`. -""" -function coords_spatial(img) - nd = ndims(img) - cd = colordim(img) - td = timedim(img) - if cd > nd || td > nd - error("Properties are inconsistent with the array dimensionality") - end - ind = [1:nd;] - if cd > td - splice!(ind, cd) - if td > 0 - splice!(ind, td) - end - elseif td > cd - splice!(ind, td) - if cd > 0 - splice!(ind, cd) - end - end - ind -end - -# size of the spatial grid -""" -``` -ssz = size_spatial(img) -``` - -Returns a tuple listing the sizes of the spatial dimensions of the image. For -example, an `Array` of size `(m,n,3)` would return `(m,n)`. - -See also: `nimages`, `width`, `height`, `widthheight`. -""" -function size_spatial(img) - sz = size(img) - sz[coords_spatial(img)] -end - -#### Utilities for writing "simple algorithms" safely #### -# If you don't feel like supporting multiple representations, call these - -""" -`assert2d(img)` triggers an error if the image has more than two spatial -dimensions or has a time dimension. -""" -function assert2d(img::AbstractArray) - if sdims(img) != 2 - error("Only two-dimensional images are supported") - end - if timedim(img) != 0 - error("Image sequences are not supported") - end -end - -""" -`assert_scalar_color(img)` triggers an error if the image uses an -array dimension to encode color. -""" -function assert_scalar_color(img::AbstractArray) - if colordim(img) != 0 - error("Only 'scalar color' is supported") - end -end - - -""" -`assert_timedim_last(img)` triggers an error if the image has a time -dimension that is not the last dimension. -""" -function assert_timedim_last(img::AbstractArray) - if 0 < timedim(img) < ndims(img) - error("Time dimension is not last") - end -end - -""" -`tf = isyfirst(img)` tests whether the first spatial dimension is `"y"`. - -See also: `isxfirst`, `assert_yfirst`. -""" -isyfirst(img::AbstractArray) = spatialorder(img)[1] == "y" -""" -`assert_yfirst(img)` triggers an error if the first spatial dimension -is not `"y"`. -""" -function assert_yfirst(img) - if !isyfirst(img) - error("Image must have y as its first dimension") - end -end - -""" -`tf = isxfirst(img)` tests whether the first spatial dimension is `"x"`. - -See also: `isyfirst`, `assert_xfirst`. -""" -isxfirst(img::AbstractArray) = spatialorder(img)[1] == "x" -""" -`assert_xfirst(img)` triggers an error if the first spatial dimension -is not `"x"`. -""" -function assert_xfirst(img::AbstractArray) - if !isxfirst(img) - error("Image must have x as its first dimension") - end -end - - - -#### Permutations over dimensions #### - -# width and height, translating "x" and "y" spatialorder into horizontal and vertical, respectively - -""" -`w, h = widthheight(img)` returns the width and height of an image, regardless of storage order. - -See also: `width`, `height`. -""" -function widthheight(img::AbstractArray, p) - c = coords_spatial(img) - size(img, c[p[1]]), size(img, c[p[2]]) -end -widthheight(img::AbstractArray) = widthheight(img, spatialpermutation(xy, img)) - - -""" -`w = width(img)` returns the horizontal size of the image, regardless -of storage order. By default horizontal corresponds to dimension -`"x"`, but see `spatialpermutation` for other options. -""" -width(img::AbstractArray) = widthheight(img)[1] -""" -`h = height(img)` returns the vertical size of the image, regardless -of storage order. By default horizontal corresponds to dimension -`"y"`, but see `spatialpermutation` for other options. -""" -height(img::AbstractArray) = widthheight(img)[2] - -""" -``` -p = spatialpermutation(to, img) -``` - -Calculates the *spatial* permutation needed to convert the spatial dimensions to -a given order. This is probably easiest to understand by examples: for an -`Array` `A` of size `(m,n,3)`, `spatialorder(A)` would yield `["y", "x"]`, so -`spatialpermutation(["y", "x"], A) = [1,2]` and `spatialpermutation(["x", "y"], -A) = [2,1]`. For an image type, here's a demonstration: - -``` -julia> Aimg = convert(Image, A) -RGB Image with: - data: 4x5x3 Array{Float64,3} - properties: - colordim: 3 - spatialorder: y x - colorspace: RGB - -julia> Ap = permutedims(Aimg, [3, 1, 2]) -RGB Image with: - data: 3x4x5 Array{Float64,3} - properties: - colordim: 1 - spatialorder: y x - colorspace: RGB - -julia> spatialpermutation(["x","y"], Ap) -2-element Array{Int64,1}: - 2 - 1 -``` -""" -spatialpermutation(to, img::AbstractArray) = default_permutation(to, spatialorder(img)) -function spatialpermutation(to, img::AbstractImage) - so = spatialorder(img) - if so != nothing - return default_permutation(to, so) - else - if sdims(img) != 2 - error("Cannot guess default spatialorder when there are more than 2 spatial dimensions") - end - return default_permutation(to, yx) - end -end - -# Permute the dimensions of an image, also permuting the relevant properties. If you have non-default properties that are vectors or matrices relative to spatial dimensions, include their names in the list of spatialprops. -permutedims(img::AbstractImage, p::Tuple{}, spatialprops::Vector = spatialproperties(img)) = img - -function permutedims(img::AbstractImage, p::Union{Vector{Int}, Tuple{Vararg{Int}}}, spatialprops::Vector = spatialproperties(img)) - if length(p) != ndims(img) - error("The permutation must have length equal to the number of dimensions") - end - if issorted(p) && length(p) == ndims(img) - return img # should we return a copy? - end - ip = invperm(to_vector(p)) - cd = colordim(img) - sd = timedim(img) - ret = copyproperties(img, permutedims(img.data, p)) - if cd > 0 - ret.properties["colordim"] = ip[cd] - p = setdiff(p, cd) - end - if sd > 0 - ret.properties["timedim"] = ip[sd] - p = setdiff(p, sd) - end - if !isempty(spatialprops) - ip = sortperm(p) - for prop in spatialprops - a = img.properties[prop] - if isa(a, AbstractVector) - ret.properties[prop] = a[ip] - elseif isa(a, AbstractMatrix) && size(a,1) == size(a,2) - ret.properties[prop] = a[ip,ip] - else - error("Do not know how to handle property ", prop) - end - end - end - ret -end - -permutedims{S<:AbstractString}(img::AbstractImage, pstr::Union{Vector{S}, Tuple{Vararg{S}}}, spatialprops::Vector = spatialproperties(img)) = permutedims(img, dimindexes(img, pstr...), spatialprops) - -if VERSION < v"0.5.0-dev" - permutedims(A::AbstractArray, p) = permutedims(convert(Array, A), p) -end - -function permutation_canonical(img) - assert2d(img) - p = spatialpermutation(spatialorder(Matrix), img) - p = coords_spatial(img)[p] - cd = colordim(img) - if cd > 0 - push!(p, cd) - end - p -end - -# Define the transpose of a 2d image -function ctranspose(img::AbstractImage) - assert2d(img) - s = coords_spatial(img) - p = collect(1:ndims(img)) - p[s] = s[2:-1:1] - permutedims(img, p) -end - -# Default list of spatial properties possessed by an image -""" -``` -sp = spatialproperties(img) -``` - -Returns all properties whose values are of the form of an array or tuple, with -one entry per spatial dimension. If you have a custom type with additional -spatial properties, you can set `img["spatialproperties"] = ["property1", -"property2", ...]`. An advantage is that functions that change spatial -dimensions, like `permutedims` and `slice`, will also adjust the properties. The -default is `["spatialorder", "pixelspacing"]`; however, if you override the -setting then these are not included automatically (you'll want to do so -manually, if applicable). -""" -function spatialproperties(img::AbstractImage) - if haskey(img, "spatialproperties") - return img.properties["spatialproperties"] - end - spatialprops = Compat.ASCIIString[] - if haskey(img, "spatialorder") - push!(spatialprops, "spatialorder") - end - if haskey(img, "pixelspacing") - push!(spatialprops, "pixelspacing") - end - if haskey(img, "spacedirections") - push!(spatialprops, "spacedirections") - end - spatialprops -end -spatialproperties(img::AbstractVector) = Compat.ASCIIString[] # these are not mutable - - -#### Low-level utilities #### -function permutation(to, from) - n = length(to) - nf = length(from) - d = Dict([(from[i], i) for i = 1:length(from)]) - ind = Array(Int, max(n, nf)) - for i = 1:n - ind[i] = get(d, to[i], 0) - end - ind[n+1:nf] = n+1:nf - ind -end - -function default_permutation(to, from) - p = permutation(to, from) - pzero = p .== 0 - if any(pzero) - p[pzero] = setdiff(1:length(to), p) - end - p -end - -function showdictlines(io::IO, dict::Dict, suppress::Set) - for (k, v) in dict - if k == "suppress" - continue - end - if !in(k, suppress) - print(io, "\n ", k, ": ") - printdictval(io, v) - else - print(io, "\n ", k, ": ") - end - end -end - -printdictval(io::IO, v) = print(io, v) -function printdictval(io::IO, v::Vector) - for i = 1:length(v) - print(io, " ") - showdictelem(io, v[i]) - end -end - -showdictelem(io::IO, v::AbstractString) = print(io, v) -showdictelem(io::IO, v) = showcompact(io, v) - -# Support indexing via -# img["t", 32, "x", 100:400] -# where anything not mentioned by name is assumed to include the whole range -function coords(img::AbstractImage, dimname::String, ind, nameind...) - c = Any[1:d for d in size(img)] - c[require_dimindex(img, dimname)] = ind - for i = 1:2:length(nameind) - c[require_dimindex(img, nameind[i])] = nameind[i+1] - end - tuple(c...) -end - -# Use keyword arguments -# e.g. coord(x=1:100, y=1:50) -function coords(img::AbstractImage; kwargs...) - c = Any[1:d for d in size(img)] - so = spatialorder(img) - for (k, v) in kwargs - c[require_dimindex(img, string(k))] = v - end - tuple(c...) -end - -require_dimindex(img::AbstractImage, dimname) = (di = dimindex(img, dimname); di > 0 || error("No dimension called ", dimname); di) - -dimindexes(img::AbstractImage, dimnames::AbstractString...) = Int[dimindex(img, nam) for nam in dimnames] - -to_vector(v::AbstractVector) = v -to_vector(v::Tuple) = [v...] - -# converts keyword argument to a dictionary -function kwargs2dict(kwargs) - d = Dict{Compat.ASCIIString,Any}() - for (k, v) in kwargs - d[string(k)] = v - end - return d -end - -n_elts{C<:Colorant}(::Type{C}) = div(sizeof(C), sizeof(eltype(C))) diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index 35e04f96..358744b9 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -29,7 +29,7 @@ facts("Algorithms") do @fact all(Images.data(img2) == Images.data(img).*A) --> true "test Txfm6a" img2 = convert(Images.Image, A) img2 = img2 .- 0.5 - img3 = 2img .* img2 + img3 = 2img .* data(img2) img2 = img ./ A img2 = (2img).^2 # Same operations with Color images From 79648ba92ec644932a4b1055ade90e19303ef3bc Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 28 Sep 2016 10:08:01 -0500 Subject: [PATCH 15/38] Change widthheight test to expect vertical-major --- test/old/core.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/old/core.jl b/test/old/core.jl index cde9076c..64a7e678 100644 --- a/test/old/core.jl +++ b/test/old/core.jl @@ -296,7 +296,7 @@ facts("Core") do end context("Spatial order, width/ height, and permutations") do - @fact widthheight(imgds) --> (3,5) "test tgY2FG" + @fact widthheight(imgds) --> (5,3) "test tgY2FG" imgp = permutedims(imgds, ["x", "y", "color"]) @fact imgp.data --> permutedims(imgds.data, [2,1,3]) "test MobXxa" imgp = permutedims(imgds, ("color", "x", "y")) From 25ab8af43eeb3ef5b2115385d19140d5585b168b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 28 Sep 2016 13:30:58 -0500 Subject: [PATCH 16/38] Deprecate the MapInfo mechanism --- src/Images.jl | 3 +- src/{map.jl => map-deprecated.jl} | 71 +++++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 13 deletions(-) rename src/{map.jl => map-deprecated.jl} (94%) diff --git a/src/Images.jl b/src/Images.jl index e0401e12..035fbd2a 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -21,6 +21,7 @@ if VERSION < v"0.5.0-dev+4490" else export float32, float64 end +using Base: depwarn using Compat import Compat.view @@ -54,7 +55,7 @@ using ImageMetadata: ImageMetaAxis using Base.Cartesian # TODO: delete this -include("map.jl") +include("map-deprecated.jl") include("overlays.jl") include("labeledarrays.jl") include("algorithms.jl") diff --git a/src/map.jl b/src/map-deprecated.jl similarity index 94% rename from src/map.jl rename to src/map-deprecated.jl index 8568bfdf..19a7440d 100644 --- a/src/map.jl +++ b/src/map-deprecated.jl @@ -57,7 +57,12 @@ map(mapi::MapInfo, A::AbstractArray) = immap(mapi, A) ## MapNone "`MapNone(T)` is a `MapInfo` object that converts `x` to have type `T`." -immutable MapNone{T} <: MapInfo{T}; end +immutable MapNone{T} <: MapInfo{T} + function MapNone() + depwarn("MapNone is deprecated, use x->$T(x)", :MapNone) + new() + end +end # Constructors MapNone{T}(::Type{T}) = MapNone{T}() @@ -104,7 +109,12 @@ because `0xa2d5>>7 == 0x0145 > typemax(UInt8)`. When applicable, the main advantage of using `BitShift` rather than `MapNone` or `ScaleMinMax` is speed. """ -immutable BitShift{T,N} <: MapInfo{T} end +immutable BitShift{T,N} <: MapInfo{T} + function BitShift() + depwarn("BitShift is deprecated, use x->x>>>$N", :BitShift) + new() + end +end BitShift{T}(::Type{T}, n::Int) = BitShift{T,n}() # note that this is not type-stable similar{S,T,N}(mapi::BitShift{S,N}, ::Type{T}, ::Type) = BitShift{T,N}() @@ -134,6 +144,11 @@ See also: `ClampMax`, `ClampMinMax`. """ immutable ClampMin{T,From} <: AbstractClamp{T} min::From + + function ClampMin(min) + depwarn("ClampMin is deprecated, use x->max(x, $min)", :ClampMin) + new(min) + end end ClampMin{T,From}(::Type{T}, min::From) = ClampMin{T,From}(min) ClampMin{T}(min::T) = ClampMin{T,T}(min) @@ -145,12 +160,22 @@ See also: `ClampMin`, `ClampMinMax`. """ immutable ClampMax{T,From} <: AbstractClamp{T} max::From + + function ClampMax(max) + depwarn("ClampMax is deprecated, use x->min(x, $max)", :ClampMax) + new(max) + end end ClampMax{T,From}(::Type{T}, max::From) = ClampMax{T,From}(max) ClampMax{T}(max::T) = ClampMax{T,T}(max) immutable ClampMinMax{T,From} <: AbstractClamp{T} min::From max::From + + function ClampMinMax(min, max) + depwarn("ClampMinMax is deprecated, use x->clamp(x, $min, $max)", :ClampMinMax) + new(min, max) + end end """ `ClampMinMax(T, minvalue, maxvalue)` is a `MapInfo` object that clamps @@ -169,7 +194,12 @@ gamut. For example, map(Clamp(RGB{U8}), RGB(1.2, -0.4, 0.6)) === RGB{U8}(1, 0, 0.6) ``` """ -immutable Clamp{T} <: AbstractClamp{T} end +immutable Clamp{T} <: AbstractClamp{T} + function Clamp() + depwarn("Clamp is deprecated, use a colorspace-specific function (clamp01 for gray/RGB)", :Clamp) + new() + end +end Clamp{T}(::Type{T}) = Clamp{T}() similar{T,F}(mapi::ClampMin, ::Type{T}, ::Type{F}) = ClampMin{T,F}(convert(F, mapi.min)) @@ -204,13 +234,10 @@ map1{CT<:AbstractRGB}(::Clamp{CT}, val::Real) = clamp01(eltype(CT), val) map1{P<:TransparentRGB}(::Clamp{P}, val::Real) = clamp01(eltype(P), val) # Also available as a stand-alone function -clamp01{T}(::Type{T}, x::Real) = convert(T, min(max(x, zero(x)), one(x))) -clamp01(x::Real) = clamp01(typeof(x), x) -clamp01(x::Colorant) = clamp01(typeof(x), x) -clamp01{Cdest<:AbstractRGB }(::Type{Cdest}, x::AbstractRGB) = (To = eltype(Cdest); - Cdest(clamp01(To, red(x)), clamp01(To, green(x)), clamp01(To, blue(x)))) -clamp01{Pdest<:TransparentRGB}(::Type{Pdest}, x::TransparentRGB) = (To = eltype(Pdest); - Pdest(clamp01(To, red(x)), clamp01(To, green(x)), clamp01(To, blue(x)), clamp01(To, alpha(x)))) +function ImageCore.clamp01{T}(::Type{T}, x::Real) + depwarn("clamp01(T, x) is deprecated, use x->T(clamp01(x))", :clamp01) + T(clamp01(x)) +end # clamp is generic for any colorspace; this version does the right thing for any RGB type clamp(x::Union{AbstractRGB, TransparentRGB}) = clamp01(x) @@ -235,6 +262,7 @@ immutable ScaleMinMax{To,From,S<:AbstractFloat} <: MapInfo{To} s::S function ScaleMinMax(min, max, s) + depwarn("ScaleMinMax is deprecated, use scaleminmax([$To,] $min, $max)", :ScaleMinMax) min >= max && error("min must be smaller than max") new(min, max, s) end @@ -295,6 +323,11 @@ color proportional to the clamped absolute value. """ immutable ScaleSigned{T, S<:AbstractFloat} <: MapInfo{T} s::S + + function ScaleSigned(s) + depwarn("ScaleSigned is deprecated, use scalesigned", :ScaleSigned) + new(s) + end end ScaleSigned{T}(::Type{T}, s::AbstractFloat) = ScaleSigned{T, typeof(s)}(s) @@ -321,7 +354,12 @@ same algorithm for `ScaleMinMax`. When displaying a movie, the min/max will be recalculated for each frame, so this can result in inconsistent contrast scaling. """ -immutable ScaleAutoMinMax{T} <: MapInfo{T} end +immutable ScaleAutoMinMax{T} <: MapInfo{T} + function ScaleAutoMinMax() + depwarn("ScaleAutoMinMax is deprecated, use scaleminmax as an argument to takemap", :ScaleAutoMinMax) + new() + end +end ScaleAutoMinMax{T}(::Type{T}) = ScaleAutoMinMax{T}() ScaleAutoMinMax() = ScaleAutoMinMax{UFixed8}() @@ -337,6 +375,10 @@ See also: `ScaleMinMax`. """ immutable ScaleMinMaxNaN{To,From,S} <: MapInfo{To} smm::ScaleMinMax{To,From,S} + function ScaleMinMaxNaN(smm) + depwarn("ScaleMinMaxNaN is deprecated, use scaleminmax in conjunction with clamp01nan or x->ifelse(isnan(x), zero(x), x)", :ScaleMinMaxNaN) + new(smm) + end end """ @@ -344,7 +386,12 @@ end that clamps grayscale or color pixels to the interval `[0,1]`, sending `NaN` pixels to zero. """ -immutable Clamp01NaN{T} <: MapInfo{T} end +immutable Clamp01NaN{T} <: MapInfo{T} + function Clamp01NaN() + depwarn("Clamp01NaN is deprecated, use clamp01nan", :Clamp01NaN) + new() + end +end Clamp01NaN{T}(A::AbstractArray{T}) = Clamp01NaN{T}() From ea15d9980c3b41d9177ee9c7e03c608f109f7a1c Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 6 Oct 2016 22:39:55 -0500 Subject: [PATCH 17/38] Get writemime tests passing --- src/Images.jl | 4 ++- src/showmime.jl | 32 +++++++++++++++++++++++ src/writemime.jl | 60 ------------------------------------------- test/old/writemime.jl | 10 +++++--- 4 files changed, 41 insertions(+), 65 deletions(-) create mode 100644 src/showmime.jl delete mode 100644 src/writemime.jl diff --git a/src/Images.jl b/src/Images.jl index 035fbd2a..60e204ee 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -62,7 +62,7 @@ include("algorithms.jl") include("exposure.jl") include("connected.jl") include("edge.jl") -include("writemime.jl") +include("showmime.jl") include("corner.jl") include("distances.jl") include("deprecated.jl") @@ -220,6 +220,8 @@ export # types # phantoms shepp_logan +_length(A::AbstractArray) = length(linearindices(A)) +_length(A) = length(A) """ `Images` is a package for representing and processing images. diff --git a/src/showmime.jl b/src/showmime.jl new file mode 100644 index 00000000..3d1e422f --- /dev/null +++ b/src/showmime.jl @@ -0,0 +1,32 @@ +### +### show as MIME type +### + +# This is used by IJulia (for example) to display images + +# mimewriteable to PNG if 2D colorant array +mimewritable{C<:Colorant}(::MIME"image/png", img::AbstractMatrix{C}) = true + +# Colors.jl turns on SVG display of colors, which leads to poor +# performance and weird spacing if you're displaying images. We need +# to disable that here. +# See https://github.com/JuliaLang/IJulia.jl/issues/229 and Images #548 +mimewritable{C<:Color}(::MIME"image/svg+xml", img::AbstractMatrix{C}) = false + +# Really large images can make display very slow, so we shrink big +# images. Conversely, tiny images don't show up well, so in such +# cases we repeat pixels. +function Base.show{C<:Colorant}(io::IO, mime::MIME"image/png", img::AbstractMatrix{C}; mapi=clamp01nan, minpixels=10^4, maxpixels=10^6) + while _length(img) > maxpixels + img = restrict(img) # big images + end + npix = _length(img) + if npix < minpixels + # Tiny images + fac = ceil(Int, sqrt(minpixels/npix)) + r = ones(Int, ndims(img)) + r[[coords_spatial(img)...]] = fac + img = repeat(img, inner=r) + end + save(Stream(format"PNG", io), img, mapi=mapi) +end diff --git a/src/writemime.jl b/src/writemime.jl deleted file mode 100644 index 209a007a..00000000 --- a/src/writemime.jl +++ /dev/null @@ -1,60 +0,0 @@ -### -### writemime -### -# only mime writeable to PNG if 2D (used by IJulia for example) -mimewritable(::MIME"image/png", img::AbstractImage) = sdims(img) == 2 && timedim(img) == 0 -mimewritable{C<:Colorant}(::MIME"image/png", img::AbstractArray{C}) = sdims(img) == 2 && timedim(img) == 0 -# Colors.jl turns on SVG display of colors, which leads to poor -# performance and weird spacing if you're displaying images. We need -# to disable that here. -# See https://github.com/JuliaLang/IJulia.jl/issues/229 and Images #548 -mimewritable(::MIME"image/svg+xml", img::AbstractImage) = false -mimewritable{C<:Color}(::MIME"image/svg+xml", img::AbstractMatrix{C}) = false - -# This is used for output by IJulia. Really large images can make -# display very slow, so we shrink big images. Conversely, tiny images -# don't show up well, so in such cases we repeat pixels. -@compat function Base.show(io::IO, mime::MIME"image/png", img::AbstractImage; mapi=mapinfo_writemime(img), minpixels=10^4, maxpixels=10^6) - assert2d(img) - A = data(img) - nc = ncolorelem(img) - npix = length(A)/nc - while npix > maxpixels - # Big images - A = restrict(A, coords_spatial(img)) - npix = length(A)/nc - end - if npix < minpixels - # Tiny images - fac = ceil(Int, sqrt(minpixels/npix)) - r = ones(Int, ndims(img)) - r[coords_spatial(img)] = fac - A = repeat(A, inner=r) - end - imgcopy = shareproperties(img, A) - save(Stream(format"PNG", io), imgcopy) -end - -@compat function Base.show(stream::IO, mime::MIME"image/png", img::AbstractImageIndexed; kwargs...) - @compat show(stream, mime, convert(Image, img); kwargs...) -end -@compat function Base.show{C<:Colorant}(stream::IO, mime::MIME"image/png", img::AbstractMatrix{C}; kwargs...) - @compat show(stream, mime, Image(img, spatialorder=["y","x"]); kwargs...) -end - -function mapinfo_writemime(img; maxpixels=10^6) - if length(img) <= maxpixels - return mapinfo_writemime_(img) - end - mapinfo_writemime_restricted(img) -end - -to_native_color{T<:Colorant}(::Type{T}) = base_color_type(T){UFixed8} -to_native_color{T<:Color}(::Type{T}) = RGB{UFixed8} -to_native_color{T<:TransparentColor}(::Type{T}) = RGBA{UFixed8} - -mapinfo_writemime_{T <:Colorant}(img::AbstractImage{T}) = Images.mapinfo(to_native_color(T), img) -mapinfo_writemime_(img::AbstractImage) = Images.mapinfo(UFixed8,img) - -mapinfo_writemime_restricted{T<:Colorant}(img::AbstractImage{T}) = ClampMinMax(to_native_color(T), 0.0, 1.0) -mapinfo_writemime_restricted(img::AbstractImage) = Images.mapinfo(UFixed8, img) diff --git a/test/old/writemime.jl b/test/old/writemime.jl index ee4b7c3a..f33ebc14 100644 --- a/test/old/writemime.jl +++ b/test/old/writemime.jl @@ -1,3 +1,5 @@ +using Images, FactCheck, Colors, FixedPointNumbers + facts("show (MIME)") do # Test that we remembered to turn off Colors.jl's colorswatch display @fact mimewritable(MIME("image/svg+xml"), rand(Gray{U8}, 5, 5)) --> false @@ -12,7 +14,7 @@ facts("show (MIME)") do A = U8[0.01 0.99; 0.25 0.75] fn = joinpath(workdir, "writemime.png") open(fn, "w") do file - @compat show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=typemax(Int)) + show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=typemax(Int)) end b = convert(Image{Gray{U8}}, load(fn)) @fact data(b) --> A @@ -21,7 +23,7 @@ facts("show (MIME)") do A = U8[0.01 0.99; 0.25 0.75] fn = joinpath(workdir, "writemime.png") open(fn, "w") do file - @compat show(file, MIME("image/png"), grayim(A), minpixels=5, maxpixels=typemax(Int)) + show(file, MIME("image/png"), grayim(A), minpixels=5, maxpixels=typemax(Int)) end b = convert(Image{Gray{U8}}, load(fn)) @fact data(b) --> A[[1,1,2,2],[1,1,2,2]] @@ -31,7 +33,7 @@ facts("show (MIME)") do Ar = restrict(A) fn = joinpath(workdir, "writemime.png") open(fn, "w") do file - @compat show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=5) + show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=5) end b = convert(Image{Gray{U8}}, load(fn)) @fact data(b) --> convert(Array{U8}, Ar) @@ -39,7 +41,7 @@ facts("show (MIME)") do abig = grayim(rand(UInt8, 1024, 1023)) fn = joinpath(workdir, "big.png") open(fn, "w") do file - @compat show(file, MIME("image/png"), abig, maxpixels=10^6) + show(file, MIME("image/png"), abig, maxpixels=10^6) end b = convert(Image{Gray{U8}}, load(fn)) abigui = convert(Array{UFixed8,2}, data(restrict(abig, (1,2)))) From e8ea87b61c71ad3e128ecd0ce0093160ac1a0710 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 6 Oct 2016 22:55:04 -0500 Subject: [PATCH 18/38] Re-fix overlay --- src/overlays.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overlays.jl b/src/overlays.jl index 00ea52f2..2b7e7869 100644 --- a/src/overlays.jl +++ b/src/overlays.jl @@ -79,7 +79,7 @@ for NC = 1:3 @inbounds @nexprs $NCm c->begin out += map(O.mapi[c], getindex(O.channels[c], $(indexargs...))) * O.colors[c] end - clamp01(eltype(O), out) + T(clamp01(out)) end end end From ad505c97a6034877b781569f3ff749337f18b5c0 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 9 Oct 2016 05:46:59 -0500 Subject: [PATCH 19/38] Reimplement overlays with StackedView --- src/Images.jl | 4 +- src/overlays-deprecated.jl | 56 +++++++++++++++++++++ src/overlays.jl | 99 -------------------------------------- test/old/overlays.jl | 63 ++++++++++++------------ 4 files changed, 90 insertions(+), 132 deletions(-) create mode 100644 src/overlays-deprecated.jl delete mode 100644 src/overlays.jl diff --git a/src/Images.jl b/src/Images.jl index 60e204ee..5f7c07b5 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -42,7 +42,7 @@ typealias RealLike Union{Real,AbstractGray} import Graphics import Graphics: width, height, Point using StatsBase # TODO: eliminate this dependency -using IndirectArrays +using IndirectArrays, MappedArrays const is_little_endian = ENDIAN_BOM == 0x04030201 @@ -56,7 +56,7 @@ using ImageMetadata: ImageMetaAxis using Base.Cartesian # TODO: delete this include("map-deprecated.jl") -include("overlays.jl") +include("overlays-deprecated.jl") include("labeledarrays.jl") include("algorithms.jl") include("exposure.jl") diff --git a/src/overlays-deprecated.jl b/src/overlays-deprecated.jl new file mode 100644 index 00000000..ce8b36ef --- /dev/null +++ b/src/overlays-deprecated.jl @@ -0,0 +1,56 @@ +function Overlay(channels::Tuple{Vararg{AbstractArray}}, colors, mapi::Tuple{Vararg{MapInfo}}) + _overlay(channels, colors, map(m->(x->immap(m, x)), mapi)) +end + +function Overlay(channels::Tuple{Vararg{AbstractArray}}, + colors, + clim = ntuple(i->(zero(eltype(channels[i])), one(eltype(channels[i]))), length(channels))) + n = length(channels) + for i = 1:n + if length(clim[i]) != 2 + error("clim must be a 2-vector") + end + end + fs = ntuple(i->scaleminmax(Float32, clim[i][1], clim[i][2]), n) + _overlay(channels, colors, fs) +end + +function _overlay(channels, colors, fs) + NC = length(colors) + length(channels) == NC || error("Number of channels must match number of colors") + length(fs) == NC || error("Number of MapInfo objects must match number of colors") + channelidx = channelassign(colors) + syms = [:zeroarray, :A, :B, :C][channelidx+1] + depwarn("Overlay(channels, colors, ...) is deprecated, please use colorview(RGB, StackedView($(syms...))), possibly in conjunction with mappedarray", :Overlay) + mc = [mappedarray(fs[i], channels[i]) for i = 1:NC] + zchannels = (zeroarray, mc...) + T = promote_type((eltype(c) for c in colors)...) + chan = zchannels[channelidx+1] + colorview(RGB, StackedView{T}(chan...)) +end + +function channelassign(colors) + n = length(colors) + channelidx = zeros(Int, 3) + for i = 1:n + col = convert(RGB, colors[i]) + setchannelidx!(channelidx, i, red(col), 1) + setchannelidx!(channelidx, i, green(col), 2) + setchannelidx!(channelidx, i, blue(col), 3) + end + channelidx +end + +function setchannelidx!(channelidx, i, val, colorindex) + if val != 0 + if channelidx[colorindex] != 0 + error("in the deprecated version, mixing in color channels is not supported, please see ??") + end + channelidx[colorindex] = i + end + channelidx +end + +nchannels{C,N,A<:StackedView}(ovr::ColorView{C,N,A}) = sum(!isa(A, ImageCore.ZeroArray) for A in ovr.parent.parents) + +OverlayImage(args...) = ImageMeta(Overlay(args...)) diff --git a/src/overlays.jl b/src/overlays.jl deleted file mode 100644 index 2b7e7869..00000000 --- a/src/overlays.jl +++ /dev/null @@ -1,99 +0,0 @@ -# An array type for colorized overlays of grayscale images -""" -``` -A = Overlay(channels, colors) -A = Overlay(channels, colors, clim) -A = Overlay(channels, colors, mapi) -``` - -Create an `Overlay` array from grayscale channels. `channels = (channel1, -channel2, ...)`, `colors` is a vector or tuple of `Color`s, and `clim` is a -vector or tuple of min/max values, e.g., `clim = ((min1,max1),(min2,max2),...)`. -Alternatively, you can supply a list of `MapInfo` objects. - -See also: `OverlayImage`. -""" -immutable Overlay{C<:RGB,N,NC,AT<:Tuple{Vararg{AbstractArray}},MITypes<:Tuple{Vararg{MapInfo}}} <: AbstractArray{C,N} - channels::AT # this holds the grayscale arrays - colors::NTuple{NC,C} - mapi::MITypes - - function Overlay(channels::Tuple{Vararg{AbstractArray}}, colors, mapi::Tuple{Vararg{MapInfo}}) - length(channels) == NC || error("Number of channels must match number of colors") - length(mapi) == NC || error("Number of ConvertInfo objects must match number of colors") - for i = 2:NC - size(channels[i]) == size(channels[1]) || error("All arrays must have the same size") - end - new(channels, colors, mapi) - end -end -Overlay(channels::Tuple{Vararg{AbstractArray}}, colors::AbstractVector, mapi::Tuple{Vararg{MapInfo}}) = - Overlay(channels,tuple(colors...),mapi) -Overlay{NC,C<:RGB}(channels::Tuple{Vararg{AbstractArray}}, colors::NTuple{NC,C}, mapi::Tuple{Vararg{MapInfo}}) = - Overlay{C,ndims(channels[1]),NC,typeof(channels),typeof(mapi)}(channels,colors,mapi) - -function Overlay(channels::Tuple{Vararg{AbstractArray}}, colors, - clim = ntuple(i->(zero(eltype(channels[i])), one(eltype(channels[i]))), length(channels))) - n = length(channels) - for i = 1:n - if length(clim[i]) != 2 - error("clim must be a 2-vector") - end - end - mapi = ntuple(i->ScaleMinMax(Float32, channels[i], clim[i][1], clim[i][2]), n) - Overlay(channels, colors, mapi) -end - -# Returns the overlay as an image, if possible -"`OverlayImage` is identical to `Overlay`, except that it returns an Image." -function OverlayImage(channels::Tuple{Vararg{AbstractArray}}, colors::Tuple{Vararg{Colorant}}, - arg = ntuple(i->(zero(eltype(channels[i])), one(eltype(channels[i]))), length(channels))) - ovr = Overlay(channels, colors, arg) - local prop - haveprop = false - for i = 1:length(channels) - if isa(channels[i], AbstractImage) - prop = copy(properties(channels[i])) - haveprop = true - break - end - end - if !haveprop - prop = properties(channels[1]) - end - haskey(prop, "colorspace") && delete!(prop, "colorspace") - haskey(prop, "colordim") && delete!(prop, "colordim") - haskey(prop, "limits") && delete!(prop, "limits") - Image(ovr, prop) -end - -for NC = 1:3 - NCm = NC-1 - for K = 1:4 - indexargs = Symbol[Symbol("i_", d) for d = 1:K] - sigargs = Expr[:($a::Integer) for a in indexargs] - @eval begin - function getindex{T,N,AT,MITypes}(O::Overlay{T,N,$NC,AT,MITypes}, $(sigargs...)) - # one of them needs a bounds-check - out = map(O.mapi[$NC], getindex(O.channels[$NC], $(indexargs...))) * O.colors[$NC] - @inbounds @nexprs $NCm c->begin - out += map(O.mapi[c], getindex(O.channels[c], $(indexargs...))) * O.colors[c] - end - T(clamp01(out)) - end - end - end -end - -setindex!(O::Overlay, val, I::Real...) = error("Overlays are read-only. Convert to Image{RGB} to adjust values.") - - -#### Other Overlay support functions #### -length(o::Overlay) = isempty(o.channels) ? 0 : length(o.channels[1]) -size(o::Overlay) = isempty(o.channels) ? (0,) : size(o.channels[1]) -size(o::Overlay, d::Integer) = isempty(o.channels) ? 0 : size(o.channels[1],d) -nchannels(o::Overlay) = length(o.channels) - -similar{T}(o::Overlay, ::Type{T}, sz::Dims) = Array(T, sz) - -showcompact(io::IO, o::Overlay) = print(io, summary(o), " with colors ", o.colors) diff --git a/test/old/overlays.jl b/test/old/overlays.jl index 31fa93ca..ee26433f 100644 --- a/test/old/overlays.jl +++ b/test/old/overlays.jl @@ -5,65 +5,66 @@ facts("Overlay") do gray = linspace(0.0, 1.0, 5) context("One") do ovr = Images.Overlay((2gray, 2gray), (RGB(1, 0, 0), RGB(0, 0, 1)), (Clamp{Float64}(), Clamp{Float64}())) - @fact ovr[1] --> RGB(0, 0, 0) - @fact ovr[2] --> RGB{U8}(0.5, 0, 0.5) - @fact ovr[3] --> ovr[4] - @fact ovr[4] --> ovr[5] - @fact ovr[5] --> exactly(RGB(1, 0, 1)) - @fact eltype(ovr) --> RGB{U8} - @fact length(ovr) --> 5 - @fact size(ovr) --> (5,) - @fact size(ovr, 1) --> 5 - @fact size(ovr, 2) --> 1 - @fact nchannels(ovr) --> 2 + @fact ovr[1] --> RGB(0, 0, 0) "test XcqmPT" + @fact ovr[2] --> RGB{U8}(0.5, 0, 0.5) "test yAFSVQ" + @fact ovr[3] --> ovr[4] "test dyKCV9" + @fact ovr[4] --> ovr[5] "test d4aUI1" + @fact ovr[5] --> exactly(RGB(1, 0, 1)) "test UF1jSj" + @fact eltype(ovr) --> RGB{U8} "test Es9OnV" + @fact length(ovr) --> 5 "test 04ptpL" + @fact size(ovr) --> (5,) "test iE5wc4" + @fact size(ovr, 1) --> 5 "test OvtQ4m" + @fact size(ovr, 2) --> 1 "test O1oIKi" + @fact nchannels(ovr) --> 2 "test vhNvBL" @fact raw(ovr) --> [0x00 0x80 0xff 0xff 0xff; 0x00 0x00 0x00 0x00 0x00; - 0x00 0x80 0xff 0xff 0xff] + 0x00 0x80 0xff 0xff 0xff] "test vLlExB" @fact separate(ovr) --> UFixed8[0 0 0; 0.5 0 0.5; 1 0 1; 1 0 1; - 1 0 1] + 1 0 1] "test XrmTTp" iob = IOBuffer() show(iob, ovr) # exercise only end context("Two") do ovr = Images.Overlay((gray, 0*gray), (RGB{UFixed8}(1, 0, 1), RGB{UFixed8}(0, 1, 0)), ((0, 1), (0, 1))) - @fact eltype(ovr) --> RGB{UFixed8} + @fact eltype(ovr) --> RGB{UFixed8} "test u7gavU" + ovr = collect(ovr) s = similar(ovr) - @fact typeof(s) --> Vector{RGB{UFixed8}} - @fact length(s) --> 5 + @fact typeof(s) --> Vector{RGB{UFixed8}} "test qOV0Iu" + @fact length(s) --> 5 "test HxDDe4" s = similar(ovr, RGB{Float32}) - @fact isa(s, Vector{RGB{Float32}}) --> true - @fact length(s) --> 5 + @fact isa(s, Vector{RGB{Float32}}) --> true "test tPX9bh" + @fact length(s) --> 5 "test CEJl6T" s = similar(ovr, RGB{Float32}, (3, 2)) - @fact isa(s, Matrix{RGB{Float32}}) --> true - @fact size(s) --> (3, 2) + @fact isa(s, Matrix{RGB{Float32}}) --> true "test qQvSYP" + @fact size(s) --> (3, 2) "test uoSePw" buf = Images.uint32color(ovr) - gray8 = [round(UInt8, 255 * x) for x in gray] - nogreen = reinterpret(RGB24, [convert(UInt32, g)<<16 | convert(UInt32, g) for g in gray8]) - @fact buf --> nogreen + gray8 = round(UInt8, 255*gray) + nogreen = [convert(UInt32, g)<<16 | convert(UInt32, g) for g in gray8] + @fact buf --> nogreen "test XOvI8c" end context("Three") do ovr = Images.Overlay((gray, 0*gray), [RGB(1, 0, 1), RGB(0, 1, 0)], ([0, 1], [0, 1])) - @fact_throws ErrorException Images.Overlay((gray, 0*gray), (RGB(1, 0, 1), RGB(0, 1, 0)), ((0,), (0, 1))) - @fact_throws ErrorException ovr[1] = RGB(0.2, 0.4, 0.6) + @fact_throws ErrorException Images.Overlay((gray, 0*gray), (RGB(1, 0, 1), RGB(0, 1, 0)), ((0,), (0, 1))) "test AKzBSc" + @fact_throws ErrorException ovr[1] = RGB(0.2, 0.4, 0.6) "test uxe7aR" end context("Four") do img1 = Images.Image(gray) ovr = Images.OverlayImage((2gray, img1), (RGB{Float32}(1, 0, 1), RGB{Float32}(0, 1, 0)), ((0, 1),(0, 1))) - @fact isa(ovr, Images.Image) --> true - @fact haskey(ovr, "colorspace") --> false - @fact Images.colorspace(ovr) --> "RGB" - @fact ovr[2] --> RGB{Float32}(0.5, 0.25, 0.5) + @fact isa(ovr, Images.Image) --> true "test AcmCRK" + @fact haskey(ovr, "colorspace") --> false "test W1xBNq" + @fact Images.colorspace(ovr) --> "RGB" "test me6TWH" + @fact ovr[2] --> RGB{Float32}(0.5, 0.25, 0.5) "test lznPxT" a = rand(Float32, 3, 2) b = rand(Float32, 3, 2) ovr = Images.OverlayImage((a, b), (RGB{Float32}(1, 0, 1), RGB{Float32}(0, 1, 0)), ((0, 1), (0, 1))) - @fact isa(ovr, Images.Image) --> true - @fact abs(ovr[1, 2] - RGB{Float32}(a[1, 2], b[1, 2], a[1, 2])) --> roughly(0, atol=1e-5) + @fact isa(ovr, Images.Image) --> true "test 8nGPCi" + @fact abs(ovr[1, 2] - RGB{Float32}(a[1, 2], b[1, 2], a[1, 2])) --> roughly(0, atol=1e-5) "test 5N74oJ" end context("permutation") do From 82bd70297cd650a95bd29186b7360766597e091f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 9 Oct 2016 06:26:39 -0500 Subject: [PATCH 20/38] Reimplement LabeledArray --- src/Images.jl | 4 +- src/deprecated.jl | 8 +++- src/labeledarrays.jl | 101 +++++++++++++++---------------------------- test/arrays.jl | 22 ++++++++++ test/runtests.jl | 2 + 5 files changed, 67 insertions(+), 70 deletions(-) create mode 100644 test/arrays.jl diff --git a/src/Images.jl b/src/Images.jl index 5f7c07b5..39178054 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -74,11 +74,9 @@ export # types ClampMinMax, Clamp, Clamp01NaN, - LabeledArray, + ColorizedArray, MapInfo, MapNone, - Overlay, - OverlayImage, ScaleAutoMinMax, ScaleMinMax, ScaleMinMaxNaN, diff --git a/src/deprecated.jl b/src/deprecated.jl index 444f6588..94659e55 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -2,7 +2,10 @@ export AbstractImage, AbstractImageDirect, AbstractImageIndexed, - Image + Image, + LabeledArray, + Overlay, + OverlayImage const yx = ["y", "x"] const xy = ["x", "y"] @@ -55,3 +58,6 @@ function magnitude_phase(img::AbstractArray, method::AbstractString, border::Abs depwarn("magnitude_phase(img, method::AbstractString, [border]) is deprecated, use magnitude_phase(img, $f, [border]) instead", :magnitude_phase) magnitude_phase(img, f, border) end + +Base.@deprecate_binding LabeledArray ColorizedArray +@deprecate ColorizedArray{T,N}(intensity::AbstractArray{T,N}, label::AbstractArray, colors::Vector{RGB}) ColorizedArray(intensity, IndirectArray(label, colors)) diff --git a/src/labeledarrays.jl b/src/labeledarrays.jl index 15dfffcb..d4766fbd 100644 --- a/src/labeledarrays.jl +++ b/src/labeledarrays.jl @@ -1,74 +1,43 @@ -type LabeledArray{T,N,A<:AbstractArray,L<:AbstractArray} <: AbstractArray{T,N} - data::A +immutable ColorizedArray{C<:Colorant,N,A<:AbstractArray,L<:AbstractArray} <: AbstractArray{C,N} + intensity::A label::L - colors::Vector{RGB} - - # Enforce that the label has Integer type - LabeledArray{Ti<:Integer}(data::AbstractArray{T,N}, label::AbstractArray{Ti}, colors::Vector{RGB}) = - new(data, label, colors) end -LabeledArray{T,N}(data::AbstractArray{T,N}, label::AbstractArray, colors::Vector{RGB}) = - LabeledArray{T,N,typeof(data),typeof(label)}(data, label, colors) -size(A::LabeledArray) = size(A.data) -size(A::LabeledArray, i::Integer) = size(A.data, i) -eltype{T}(A::LabeledArray{T}) = T -ndims{T,N}(A::LabeledArray{T,N}) = N +""" + ColorizedArray(intensity, label::IndirectArray) -> A + +Create an array, combining a `label` array (where each pixel is +assigned one of a list of discrete colors) and an `intensity` array +(where each pixel has a scalar value). `A` satisfies + + A[i,j,...] = intensity[i,j,...] * label[i,j,...] -for N = 1:4 - @eval begin - # All but the last of these are inside the @eval loop simply to avoid ambiguity warnings. - # These first two are additional ones needed to avoid ambiguity warnings. - _uint32color_gray!{T}(buf::Array{UInt32}, A::LabeledArray{T,$N}, mapi::ScaleSigned) = error("Cannot use ScaleSigned with a labeled array") - _uint32color_gray!{T,L<:LabeledArray}(buf::Array{UInt32}, A::SubArray{T,$N,L}, mapi::ScaleSigned) = error("Cannot use ScaleSigned with a labeled array") +The label array "tinges" the grayscale intensity with the color +associated with that point's label. - function _uint32color_gray!{T}(buf::Array{UInt32}, A::LabeledArray{T,$N}, mapi::MapInfo = mapinfo(UInt8, A)) - if size(buf) != size(A) - error("Size mismatch") - end - dat = A.data - label = A.label - col = A.colors - for i = 1:length(dat) - gr = map(mapi, dat[i]) - lbl = label[i] - if lbl == 0 - buf[i] = rgb24(gr,gr,gr) - else - buf[i] = convert(RGB24, gr*col[lbl]) - end - end - buf - end +This computation is performed lazily, as to be suitable even for large arrays. +""" +function ColorizedArray{C<:Colorant,N}(intensity, label::IndirectArray{C,N}) + indices(intensity) == indices(label) || throw(DimensionMismatch("intensity and label must have the same indices, got $(indices(intensity)) and $(indices(label))")) + CI = typeof(one(C)*zero(eltype(intensity))) + ColorizedArray{CI,N,typeof(intensity),typeof(label)}(intensity, label) +end + +# TODO: an implementation involving AxisArray that matches the shared +# axes, and therefore allows `label` to be of lower dimensionality +# than `intensity`. - # For SubArrays, we can't efficiently use linear indexing, and in any event - # we want to broadcast label where necessary - function _uint32color_gray!{T,A<:LabeledArray}(buf::Array{UInt32}, S::SubArray{T,$N,A}, mapi::MapInfo = mapinfo(UInt8, A)) - if size(buf) != size(S) - error("Size mismatch") - end - indexes = S.indexes - dat = slice(S.parent.data, indexes) - plabel = S.parent.label - newindexes = RangeIndex[size(plabel,i)==1 ? (isa(indexes[i], Int) ? 1 : (1:1)) : indexes[i] for i = 1:ndims(plabel)] - label = slice(plabel, newindexes...) - col = S.parent.colors - _uint32color_labeled(buf, dat, label, col, mapi) # type of label can't be inferred, use function boundary - end +Base.size(A::ColorizedArray) = size(A.intensity) +Base.indices(A::ColorizedArray) = indices(A.intensity) +Base.linearindexing(A::ColorizedArray) = Base.linearindexing(Base.linearindexing(A.intensity), Base.linearindexing(A.label)) - function _uint32color_labeled{T}(buf, dat::AbstractArray{T,$N}, label, col, mapi) - k = 0 - @inbounds @nloops $N i buf begin - val = @nref $N dat i - gr = map(mapi, val) - lbl = @nref $N label i - if lbl == 0 - buf[k+=1] = rgb24(gr,gr,gr) - else - buf[k+=1] = convert(RGB24, gr*col[lbl]) - end - end - buf - end - end +@inline function Base.getindex(A::ColorizedArray, i::Integer) + @boundscheck checkbounds(A, i) + @inbounds ret = A.intensity[i]*A.label[i] + ret +end +@inline function Base.getindex{C,N}(A::ColorizedArray{C,N}, I::Vararg{Int,N}) + @boundscheck checkbounds(A, I...) + @inbounds ret = A.intensity[I...]*A.label[I...] + ret end diff --git a/test/arrays.jl b/test/arrays.jl new file mode 100644 index 00000000..cd40ad22 --- /dev/null +++ b/test/arrays.jl @@ -0,0 +1,22 @@ +using Images, Colors, IndirectArrays, Base.Test + +@testset "ColorizedArray" begin + intensity = [0.1 0.3; 0.2 0.4] + labels = IndirectArray([1 2; 2 1], [RGB(1,0,0), RGB(0,0,1)]) + A = ColorizedArray(intensity, labels) + target = intensity .* labels + @test eltype(A) == RGB{Float64} + @test size(A) == (2,2) + @test indices(A) == (Base.OneTo(2), Base.OneTo(2)) + for i = 1:4 + @test A[i] === target[i] + end + for j = 1:2, i = 1:2 + @test A[i,j] === target[i,j] + end + for (a,t) in zip(A, target) + @test a === t + end +end + +nothing diff --git a/test/runtests.jl b/test/runtests.jl index 09dd9926..a7359a5e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1 +1,3 @@ +include("arrays.jl") + include("old/runtests.jl") From 7cf82f1823ce8c533f86bec3ce219929fed9c738 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 11 Oct 2016 14:43:52 -0500 Subject: [PATCH 21/38] Update REQUIRE --- REQUIRE | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/REQUIRE b/REQUIRE index 93459304..ca86af6f 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,7 +1,15 @@ -julia 0.4 +julia 0.5 +Reexport Colors 0.6 ColorVectorSpace 0.1 FixedPointNumbers 0.1.0 0.3.0 +ImageCore +ImageFiltering +AxisArrays +ImageAxes +ImageMetadata +IndirectArrays +MappedArrays SIUnits Zlib Graphics 0.1 From 1959befd2ab6c3aee59b9d314de9bd07d0be316d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 11 Oct 2016 15:55:08 -0500 Subject: [PATCH 22/38] Some fixes to avoid warnings from RGB24/UInt32 convert->reinterpret deprecation --- src/map-deprecated.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/map-deprecated.jl b/src/map-deprecated.jl index 19a7440d..0d720cca 100644 --- a/src/map-deprecated.jl +++ b/src/map-deprecated.jl @@ -77,6 +77,11 @@ map1(mapi::Union{MapNone{RGB24}, MapNone{ARGB32}}, b::Bool) = ifelse(b, 0xffuf8, map1(mapi::Union{MapNone{RGB24},MapNone{ARGB32}}, val::Fractional) = convert(UFixed8, val) map1{CT<:Colorant}(mapi::MapNone{CT}, val::Fractional) = convert(eltype(CT), val) +immap(::MapNone{UInt32}, val::RGB24) = val.color +immap(::MapNone{UInt32}, val::ARGB32) = val.color +immap(::MapNone{RGB24}, val::UInt32) = reinterpret(RGB24, val) +immap(::MapNone{ARGB32}, val::UInt32) = reinterpret(ARGB32, val) + # immap{T<:Colorant}(mapi::MapNone{T}, img::AbstractImageIndexed{T}) = convert(Image{T}, img) # immap{C<:Colorant}(mapi::MapNone{C}, img::AbstractImageDirect{C}) = img # ambiguity resolution immap{T}(mapi::MapNone{T}, img::AbstractArray{T}) = img From 3272083cea1b76b9074fa9b17e62d11098c9013c Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 11 Oct 2016 19:40:26 -0500 Subject: [PATCH 23/38] Tweak .travis.yml for images-next --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index db63f288..43acccdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ os: - linux - osx julia: - - 0.4 - 0.5 - nightly notifications: @@ -11,6 +10,7 @@ notifications: script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - julia -e 'Pkg.clone(pwd()); Pkg.build("Images")' + - julia -e 'Pkg.add("ImageMagick"); Pkg.checkout("ImageMagick", "images-next")' - julia -e 'Pkg.test("Images", coverage=true)' after_success: - if [ $TRAVIS_JULIA_VERSION = "0.4" ] && [ $TRAVIS_OS_NAME = "linux" ]; then From 21e1870455006b1b2e1f9442b439c7bd6b66b50c Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 11 Oct 2016 19:53:35 -0500 Subject: [PATCH 24/38] Update the NEWS --- NEWS.md | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4529e946..471b0c87 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,8 +4,8 @@ Images has been rewritten essentially from scratch for this release. The major goals of the release are: - More consistent treatment of spatial orientation -- Stop losing key properties upon indexing -- More sensible treatment of indexed (colormap) images +- Preserve key properties upon indexing +- Intuitive handling of indexed (colormap) images - Better support for a wider range of array types - Improvements in type-stability of many operations - Improvements in the user experience through easier interfaces, more @@ -28,18 +28,21 @@ Key changes (of which many are breaking): `ImageMeta`, and should be much less needed now that most properties can be encoded with `AxisArrays`. `ImageMeta` is still useful if you need to encode information like date/time at which the image was - taken, sky coordinates, patient IDs, or experimental conditions. + taken, sky coordinates, patient IDs, or experimental + conditions. Otherwise, it's recommended to use regular `Array`s, or + `AxisArrays` if you need to endow axes with "meaning." - Full commitment to the use of `Colorant` types (as defined by the `ColorTypes` and `Colors` packages) for encoding color images. Arrays are no longer allowed to declare that they use one axis (dimension) to store color information, i.e., a `m×n×3 Float32` - array would be a 3d grayscale image, not an RGB image. This choice - facilitates efficient and type-stable indexing behavior and enhances - consistency. "Lazy" interconversion between arbitrary numeric - arrays and color arrays are provided by two new view types, - `colorview` and `channelview`, defined in the `ImageCore` package. - These types hopefully remove any awkwardness from the new requirement. + array would be displayed as a 3d grayscale image, not an RGB + image. This choice facilitates efficient and type-stable indexing + behavior and enhances consistency. "Lazy" interconversion between + arbitrary numeric arrays and color arrays are provided by two new + view types, `colorview` and `channelview`, defined in the + `ImageCore` package. These types hopefully remove any awkwardness + from the new requirement. - For an indexed (colormap) image `imgi`, indexing with `imgi[i,j]` used to return the *index*, not the *pixel value*. This operation now @@ -72,6 +75,26 @@ Key changes (of which many are breaking): is still used when applicable.) Consequently, this release features better support for a wider range of array types. +- Several improvements have been made to the handling of fixed-point + numbers, which permit the use of 8- and 16-bit types that act + similarly to floating-point numbers and which permit a consistent + criterion for "black" (0.0) and "white" (1.0) independent of storage + type. Specifically: + + + Trying to convert out-of-bounds values now gives an informative + error message rather than just `InexactError` + + Several bugs in FixedPointNumber operations have been fixed, and + such operations are more consistent about return types + + A compact printing scheme is being tested in the + `teh/compact_printing` branch; check out the `fixed-renaming` + branch of many other packages to account for deprecations + +- A new package, `ImageTransformations`, is underway for rotation, + resizing, and other geometric operations on images. + +- Many deprecation warnings were designed to help users of the current + Images package transition to the new framework. + Other changes: - The gradient components returned by `imgradients` match the @@ -85,15 +108,15 @@ Other changes: 1/8 compared to earlier releases. - `extrema_filter` has been deprecated in favor of - `rankfilter(extrema, A, window)`. However, this returns an array of + `mapwindow(extrema, A, window)`. However, this returns an array of `(min,max)` tuples rather than separate `min`, `max` arrays. This is intended to transition towards a future API where one can pass `min` or `max` in place of `extrema` to obtain just one of these. Currently, you can retrieve the `min` array with `first.(mm)` and the `max` array with `last.(mm)`. -- `extrema_filter` discards the edges of the image, whereas - `rankfilter` returns an array of the same size as the input. +- The old `extrema_filter` discards the edges of the image, whereas + the new one (based on `mapwindow`) returns an array of the same size as the input. # Older versions From e914b5e6ecd7954714399b531a97d611c45f4018 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 16 Oct 2016 07:57:33 -0500 Subject: [PATCH 25/38] Send 8-bit colors to Jupyter --- src/showmime.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/showmime.jl b/src/showmime.jl index 3d1e422f..2b68b342 100644 --- a/src/showmime.jl +++ b/src/showmime.jl @@ -16,7 +16,7 @@ mimewritable{C<:Color}(::MIME"image/svg+xml", img::AbstractMatrix{C}) = false # Really large images can make display very slow, so we shrink big # images. Conversely, tiny images don't show up well, so in such # cases we repeat pixels. -function Base.show{C<:Colorant}(io::IO, mime::MIME"image/png", img::AbstractMatrix{C}; mapi=clamp01nan, minpixels=10^4, maxpixels=10^6) +function Base.show{C<:Colorant}(io::IO, mime::MIME"image/png", img::AbstractMatrix{C}; mapi=x->map8(clamp01nan(x)), minpixels=10^4, maxpixels=10^6) while _length(img) > maxpixels img = restrict(img) # big images end @@ -30,3 +30,7 @@ function Base.show{C<:Colorant}(io::IO, mime::MIME"image/png", img::AbstractMatr end save(Stream(format"PNG", io), img, mapi=mapi) end + +# Jupyter seemingly can't handle 16-bit colors +map8(c::Colorant) = mapc(UFixed8, c) +map8(x::Number) = UFixed8(x) From 51784f328d9b18130f707f4b1e16df0e64102f58 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Oct 2016 10:34:03 -0500 Subject: [PATCH 26/38] Move MapInfo exports to deprecated.jl --- src/Images.jl | 13 ------------- src/deprecated.jl | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index 39178054..4c9c61d5 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -68,20 +68,7 @@ include("distances.jl") include("deprecated.jl") export # types - BitShift, - ClampMin, - ClampMax, - ClampMinMax, - Clamp, - Clamp01NaN, ColorizedArray, - MapInfo, - MapNone, - ScaleAutoMinMax, - ScaleMinMax, - ScaleMinMaxNaN, - ScaleSigned, - SliceData, # macros @test_approx_eq_sigma_eps, diff --git a/src/deprecated.jl b/src/deprecated.jl index 94659e55..1813b7b9 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -5,7 +5,20 @@ export Image, LabeledArray, Overlay, - OverlayImage + OverlayImage, + BitShift, + ClampMin, + ClampMax, + ClampMinMax, + Clamp, + Clamp01NaN, + MapInfo, + MapNone, + ScaleAutoMinMax, + ScaleMinMax, + ScaleMinMaxNaN, + ScaleSigned, + SliceData const yx = ["y", "x"] const xy = ["x", "y"] From 9ec928718e3c342e70b56bf0547e76a6441f3419 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Oct 2016 16:26:22 -0500 Subject: [PATCH 27/38] Fix change in broadcast in julia 0.6 --- src/algorithms.jl | 9 +++++++-- test/old/algorithms.jl | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 28d6d8f4..6b903474 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -18,7 +18,7 @@ end _meanfinite(A::AbstractArray, ::Type, region) = mean(A, region) # non floating-point using Base: check_reducedims, reducedim1, safe_tail -using Base.Broadcast: newindex, newindexer +using Base.Broadcast: newindex """ sumfinite!(S, K, A) @@ -37,7 +37,7 @@ function sumfinite!{T,N}(S, K, A::AbstractArray{T,N}) indices(S) == indices(K) || throw(DimensionMismatch("S and K must have identical indices")) indsAt, indsSt = safe_tail(indices(A)), safe_tail(indices(S)) - keep, Idefault = newindexer(indsAt, indsSt) + keep, Idefault = _newindexer(indsAt, indsSt) if reducedim1(S, A) # keep the accumulators as a local variable when reducing along the first dimension i1 = first(indices1(S)) @@ -67,6 +67,11 @@ function sumfinite!{T,N}(S, K, A::AbstractArray{T,N}) end S, K end +if VERSION < v"0.6.0-dev.693" + _newindexer(shape, inds) = Base.Broadcast.newindexer(shape, inds) +else + _newindexer(shape, inds) = Base.Broadcast.shapeindexer(shape, inds) +end # Entropy for grayscale (intensity) images function _log(kind::Symbol) diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index 358744b9..64247b1d 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -69,7 +69,9 @@ facts("Algorithms") do @fact Images.meanfinite(A, 1) --> roughly([2]) "test jy9vvu" A = [NaN 1 2 3; NaN 6 5 4] - @test_approx_eq Images.meanfinite(A, 1) [NaN 3.5 3.5 3.5] + mf = Images.meanfinite(A, 1) + @fact isnan(mf[1]) --> true "test meanfiniteNaN" + @test_approx_eq mf[1,2:end] [3.5 3.5 3.5] @test_approx_eq Images.meanfinite(A, 2) [2, 5]' @test_approx_eq Images.meanfinite(A, (1,2)) [3.5] @fact Images.minfinite(A) --> 1 "test ctthKJ" From 932b9c2aa25e296c6727a412f98e9dc1b838150b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Oct 2016 20:45:26 -0500 Subject: [PATCH 28/38] Rework interface to blob_LoG and reimplement findlocalextrema --- NEWS.md | 9 +++- src/Images.jl | 1 + src/algorithms.jl | 96 ++++++++++++++++++++++++++++++------------ test/algorithms.jl | 39 +++++++++++++++++ test/old/algorithms.jl | 14 ------ test/runtests.jl | 1 + 6 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 test/algorithms.jl diff --git a/NEWS.md b/NEWS.md index 471b0c87..250580b3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -95,7 +95,7 @@ Key changes (of which many are breaking): - Many deprecation warnings were designed to help users of the current Images package transition to the new framework. -Other changes: +Other changes (all of which are breaking): - The gradient components returned by `imgradients` match the dimensions of the input; in `g1, g2, ... = imgradients(img, @@ -118,6 +118,13 @@ Other changes: - The old `extrema_filter` discards the edges of the image, whereas the new one (based on `mapwindow`) returns an array of the same size as the input. +- The output of `blob_LoG` is now a `Vector{BlobLoG}`, a new exported + immutable, rather than the old tuple format. + +- `findlocalextrema` now returns a `Vector{CartesianIndex{N}}` rather + than a `Vector{NTuple{N,Int}}`. This makes it ready for use in efficient + indexing. + # Older versions For earlier history, please see the git revision history. diff --git a/src/Images.jl b/src/Images.jl index 4c9c61d5..d8c64566 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -68,6 +68,7 @@ include("distances.jl") include("deprecated.jl") export # types + BlobLoG, ColorizedArray, # macros diff --git a/src/algorithms.jl b/src/algorithms.jl index 6b903474..3c1df177 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -360,48 +360,90 @@ function test_approx_eq_sigma_eps{T<:Real}(A::AbstractArray, B::AbstractArray, end """ - blob_LoG(img, sigmas) -> Vector{Tuple} +BlobLoG stores information about the location of peaks as discovered by `blob_LoG`. +It has fields: -Find "blobs" in an N-D image using Lapacian of Gaussians at the specifed -sigmas. Returned are the local maxima's heights, radii, and spatial coordinates. +- location: the location of a peak in the filtered image (a CartesianIndex) +- σ: the value of σ which lead to the largest `-LoG`-filtered amplitude at this location +- amplitude: the value of the `-LoG(σ)`-filtered image at the peak -See Lindeberg T (1998), "Feature Detection with Automatic Scale Selection", +Note that the radius is equal to σ√2. +""" +immutable BlobLoG{T,S,N} + location::CartesianIndex{N} + σ::S + amplitude::T +end + +""" + blob_LoG(img, σs, [edges]) -> Vector{BlobLoG} + +Find "blobs" in an N-D image using the negative Lapacian of Gaussians +with the specifed vector or tuple of σ values. The algorithm searches for places +where the filtered image (for a particular σ) is at a peak compared to all +spatially- and σ-adjacent voxels. + +The optional `edges` argument controls whether peaks on the edges are +included. `edges` can be `true` or `false`, or a N+1-tuple in which +the first entry controls whether edge-σ values are eligible to serve +as peaks, and the remaining N entries control each of the N dimensions +of `img`. + +# Citation: + +Lindeberg T (1998), "Feature Detection with Automatic Scale Selection", International Journal of Computer Vision, 30(2), 79–116. -Note that only 2-D images are currently supported due to a limitation of `imfilter_LoG`. +See also: BlobLoG. """ -function blob_LoG{T,N}(img::AbstractArray{T,N}, sigmas) +function blob_LoG{T,N}(img::AbstractArray{T,N}, σs, edges::Tuple{Vararg{Bool}}=(true, ntuple(d->false, Val{N})...)) + sigmas = sort(σs) img_LoG = Array(Float64, length(sigmas), size(img)...) + colons = ntuple(d->Colon(), Val{N}) @inbounds for isigma in eachindex(sigmas) - img_LoG[isigma,:] = sigmas[isigma] * imfilter_LoG(img, sigmas[isigma]) + img_LoG[isigma,colons...] = (-sigmas[isigma]) * imfilter(img, Kernel.LoG(sigmas[isigma])) end - - radii = sqrt(2.0)*sigmas - maxima = findlocalmaxima(img_LoG, 1:ndims(img_LoG), (true, falses(N)...)) - [(img_LoG[x...], radii[x[1]], tail(x)...) for x in maxima] + maxima = findlocalmaxima(img_LoG, 1:ndims(img_LoG), edges) + [BlobLoG(CartesianIndex(tail(x.I)), sigmas[x[1]], img_LoG[x]) for x in maxima] end - -findlocalextrema{T,N}(img::AbstractArray{T,N}, region, edges::Bool, order) = findlocalextrema(img, region, ntuple(d->edges,N), order) - -# FIXME -@generated function findlocalextrema{T,N}(img::AbstractArray{T,N}, region::Union{Tuple{Int,Vararg{Int}},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) - quote - issubset(region,1:ndims(img)) || throw(ArgumentError("Invalid region.")) - extrema = Tuple{(@ntuple $N d->Int)...}[] - @inbounds @nloops $N i d->((1+!edges[d]):(size(img,d)-!edges[d])) begin - isextrema = true - img_I = (@nref $N img i) - @nloops $N j d->(in(d,region) ? (max(1,i_d-1):min(size(img,d),i_d+1)) : i_d) begin - (@nall $N d->(j_d == i_d)) && continue - if !Base.Order.lt(order, (@nref $N img j), img_I) +blob_LoG{T,N}(img::AbstractArray{T,N}, σs, edges::Bool) = blob_LoG(img, σs, (edges, ntuple(d->edges,Val{N})...)) + +findlocalextrema{T,N}(img::AbstractArray{T,N}, region, edges::Bool, order) = findlocalextrema(img, region, ntuple(d->edges,Val{N}), order) + +function findlocalextrema{T<:Union{Gray,Number},N}(img::AbstractArray{T,N}, region::Union{Tuple{Int,Vararg{Int}},Vector{Int},UnitRange{Int},Int}, edges::NTuple{N,Bool}, order::Base.Order.Ordering) + issubset(region,1:ndims(img)) || throw(ArgumentError("invalid region")) + extrema = Array{CartesianIndex{N}}(0) + edgeoffset = CartesianIndex(map(!, edges)) + R0 = CartesianRange(indices(img)) + R = CartesianRange(R0.start+edgeoffset, R0.stop-edgeoffset) + Rinterior = CartesianRange(R0.start+1, R0.stop-1) + iregion = CartesianIndex(ntuple(d->d∈region, Val{N})) + Rregion = CartesianRange(-iregion, iregion) + z = zero(iregion) + for i in R + isextrema = true + img_i = img[i] + if i ∈ Rinterior + # If i is in the interior, we don't have to worry about i+j being out-of-bounds + for j in Rregion + j == z && continue + if !Base.Order.lt(order, img[i+j], img_i) + isextrema = false + break + end + end + else + for j in Rregion + (j == z || i+j ∉ R0) && continue + if !Base.Order.lt(order, img[i+j], img_i) isextrema = false break end end - isextrema && push!(extrema, (@ntuple $N d->(i_d))) end - extrema + isextrema && push!(extrema, i) end + extrema end """ diff --git a/test/algorithms.jl b/test/algorithms.jl new file mode 100644 index 00000000..f2aa7a3d --- /dev/null +++ b/test/algorithms.jl @@ -0,0 +1,39 @@ +using Images +using Base.Test + +@testset "Algorithms" begin + @testset "Features" begin + A = zeros(Int, 9, 9); A[5, 5] = 1 + blobs = blob_LoG(A, 2.0.^[0.5,0,1]) + @test length(blobs) == 1 + blob = blobs[1] + @test blob.amplitude ≈ 0.3183098861837907 + @test blob.σ === 1.0 + @test blob.location == CartesianIndex((5,5)) + @test blob_LoG(A, [1.0]) == blobs + @test blob_LoG(A, [1.0], (true, false, false)) == blobs + @test isempty(blob_LoG(A, [1.0], false)) + A = zeros(Int, 9, 9); A[1, 5] = 1 + blobs = blob_LoG(A, 2.0.^[0,0.5,1]) + A = zeros(Int, 9, 9); A[1,5] = 1 + blobs = blob_LoG(A, 2.0.^[0.5,0,1]) + @test all(b.amplitude < 1e-16 for b in blobs) + blobs = filter(b->b.amplitude > 0.1, blob_LoG(A, 2.0.^[0.5,0,1], true)) + @test length(blobs) == 1 + @test blobs[1].location == CartesianIndex((1,5)) + @test filter(b->b.amplitude > 0.1, blob_LoG(A, 2.0.^[0.5,0,1], (true, true, false))) == blobs + @test isempty(blob_LoG(A, 2.0.^[0,1], (false, true, false))) + blobs = blob_LoG(A, 2.0.^[0,0.5,1], (true, false, true)) + @test all(b.amplitude < 1e-16 for b in blobs) + A = zeros(Int, 9, 9); A[[1:2;5],5]=1 + @test findlocalmaxima(A) == [CartesianIndex((5,5))] + @test findlocalmaxima(A,2) == [CartesianIndex((1,5)),CartesianIndex((2,5)),CartesianIndex((5,5))] + @test findlocalmaxima(A,2,false) == [CartesianIndex((2,5)),CartesianIndex((5,5))] + A = zeros(Int, 9, 9, 9); A[[1:2;5],5,5]=1 + @test findlocalmaxima(A) == [CartesianIndex((5,5,5))] + @test findlocalmaxima(A,2) == [CartesianIndex((1,5,5)),CartesianIndex((2,5,5)),CartesianIndex((5,5,5))] + @test findlocalmaxima(A,2,false) == [CartesianIndex((2,5,5)),CartesianIndex((5,5,5))] + end +end + +nothing diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index 64247b1d..ed7a46a5 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -213,20 +213,6 @@ facts("Algorithms") do @fact img2 --> roughly(A) "test D74cjO" end - context("Features") do - A = zeros(Int, 9, 9); A[5, 5] = 1 - @fact all(x->x true "test IIHdVc" - @fact all(x->x true "test CMOJKr" - A = zeros(Int, 9, 9); A[[1:2;5],5]=1 - @fact findlocalmaxima(A) --> [(5,5)] "test xyncRi" - @fact findlocalmaxima(A,2) --> [(1,5),(2,5),(5,5)] "test vRMsHj" - @fact findlocalmaxima(A,2,false) --> [(2,5),(5,5)] "test NpwSel" - A = zeros(Int, 9, 9, 9); A[[1:2;5],5,5]=1 - @fact findlocalmaxima(A) --> [(5,5,5)] "test lSv2tA" - @fact findlocalmaxima(A,2) --> [(1,5,5),(2,5,5),(5,5,5)] "test 4jrt8N" - @fact findlocalmaxima(A,2,false) --> [(2,5,5),(5,5,5)] "test 2awiPo" - end - context("Restriction") do imgcol = Images.colorim(rand(3,5,6)) A = reshape([convert(UInt16, i) for i = 1:60], 4, 5, 3) diff --git a/test/runtests.jl b/test/runtests.jl index a7359a5e..cd9c8af4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,4 @@ include("arrays.jl") +include("algorithms.jl") include("old/runtests.jl") From 13742a8e5831562b20113b32447a19199b7c94d5 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 28 Jan 2017 10:24:29 -0600 Subject: [PATCH 29/38] Fix deprecations for compact printing in FixedPointNumbers --- REQUIRE | 6 +- src/map-deprecated.jl | 107 +++++++++++++------------- src/showmime.jl | 4 +- test/old/algorithms.jl | 32 ++++---- test/old/core.jl | 66 ++++++++-------- test/old/edge.jl | 3 +- test/old/exposure.jl | 62 +++++++-------- test/old/map.jl | 170 ++++++++++++++++++++--------------------- test/old/overlays.jl | 12 +-- test/old/writemime.jl | 26 +++---- 10 files changed, 241 insertions(+), 247 deletions(-) diff --git a/REQUIRE b/REQUIRE index ca86af6f..67e5b1d2 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,8 +1,8 @@ julia 0.5 Reexport -Colors 0.6 -ColorVectorSpace 0.1 -FixedPointNumbers 0.1.0 0.3.0 +Colors 0.7.0 +ColorVectorSpace 0.2 +FixedPointNumbers 0.3.0 ImageCore ImageFiltering AxisArrays diff --git a/src/map-deprecated.jl b/src/map-deprecated.jl index 0d720cca..39fc5e0e 100644 --- a/src/map-deprecated.jl +++ b/src/map-deprecated.jl @@ -41,7 +41,7 @@ end Base.map{C}(::ColorSaturated{C}, val::Union{Number,Gray}) = ifelse(val == 1, C(1,0,0), C(val,val,val)) -imgc = map(ColorSaturated{RGB{U8}}(), img) +imgc = map(ColorSaturated{RGB{N0f8}}(), img) ``` For pre-defined types see `MapNone`, `BitShift`, `ClampMinMax`, `ScaleMinMax`, @@ -74,7 +74,7 @@ similar{T}(mapi::MapNone, ::Type{T}, ::Type) = MapNone{T}() # Implementation immap{T}(mapi::MapNone{T}, val::Union{Number,Colorant}) = convert(T, val) map1(mapi::Union{MapNone{RGB24}, MapNone{ARGB32}}, b::Bool) = ifelse(b, 0xffuf8, 0x00uf8) -map1(mapi::Union{MapNone{RGB24},MapNone{ARGB32}}, val::Fractional) = convert(UFixed8, val) +map1(mapi::Union{MapNone{RGB24},MapNone{ARGB32}}, val::Fractional) = convert(N0f8, val) map1{CT<:Colorant}(mapi::MapNone{CT}, val::Fractional) = convert(eltype(CT), val) immap(::MapNone{UInt32}, val::RGB24) = val.color @@ -86,11 +86,6 @@ immap(::MapNone{ARGB32}, val::UInt32) = reinterpret(ARGB32, val) # immap{C<:Colorant}(mapi::MapNone{C}, img::AbstractImageDirect{C}) = img # ambiguity resolution immap{T}(mapi::MapNone{T}, img::AbstractArray{T}) = img -immap(::MapNone{UInt32}, val::RGB24) = val.color -immap(::MapNone{UInt32}, val::ARGB32) = val.color -immap(::MapNone{RGB24}, val::UInt32) = reinterpret(RGB24, val) -immap(::MapNone{ARGB32}, val::UInt32) = reinterpret(ARGB32, val) - ## BitShift """ @@ -99,14 +94,14 @@ It is particularly useful in converting high bit-depth images to 8-bit images for the purpose of display. For example, ``` -map(BitShift(UFixed8, 8), 0xa2d5uf16) === 0xa2uf8 +map(BitShift(N0f8, 8), 0xa2d5uf16) === 0xa2uf8 ``` -converts a `UFixed16` to the corresponding `UFixed8` by discarding the +converts a `N0f16` to the corresponding `N0f8` by discarding the least significant byte. However, ``` -map(BitShift(UFixed8, 7), 0xa2d5uf16) == 0xffuf8 +map(BitShift(N0f8, 7), 0xa2d5uf16) == 0xffuf8 ``` because `0xa2d5>>7 == 0x0145 > typemax(UInt8)`. @@ -132,7 +127,7 @@ immap{T<:Real,N}(mapi::BitShift{T,N}, val::Real) = _immap(T, BS{N}, val) immap{T<:Real,N}(mapi::BitShift{T,N}, val::Gray) = _immap(T, BS{N}, val.val) immap{T<:Real,N}(mapi::BitShift{Gray{T},N}, val::Gray) = Gray(_immap(T, BS{N}, val.val)) map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::Unsigned) = _immap(UInt8, BS{N}, val) -map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::UFixed) = _immap(UFixed8, BS{N}, val) +map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::UFixed) = _immap(N0f8, BS{N}, val) map1{CT<:Colorant,N}(mapi::BitShift{CT,N}, val::UFixed) = _immap(eltype(CT), BS{N}, val) @@ -196,7 +191,7 @@ ClampMinMax{T}(min::T, max::T) = ClampMinMax{T,T}(min,max) gamut. For example, ``` -map(Clamp(RGB{U8}), RGB(1.2, -0.4, 0.6)) === RGB{U8}(1, 0, 0.6) +map(Clamp(RGB{N0f8}), RGB(1.2, -0.4, 0.6)) === RGB{N0f8}(1, 0, 0.6) ``` """ immutable Clamp{T} <: AbstractClamp{T} @@ -225,9 +220,9 @@ immap{T<:Fractional,F<:Fractional}(mapi::ClampMinMax{Gray{T},F}, val::Gray{F}) = immap{T<:Fractional,F<:Fractional}(mapi::ClampMin{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T}, max(val, mapi.min)) immap{T<:Fractional,F<:Fractional}(mapi::ClampMax{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T}, min(val, mapi.max)) immap{T<:Fractional,F<:Fractional}(mapi::ClampMinMax{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T},min(max(val, mapi.min), mapi.max)) -map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMin{T,F}, val::F) = convert(UFixed8, max(val, mapi.min)) -map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMax{T,F}, val::F) = convert(UFixed8, min(val, mapi.max)) -map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMinMax{T,F}, val::F) = convert(UFixed8,min(max(val, mapi.min), mapi.max)) +map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMin{T,F}, val::F) = convert(N0f8, max(val, mapi.min)) +map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMax{T,F}, val::F) = convert(N0f8, min(val, mapi.max)) +map1{T<:Union{RGB24,ARGB32},F<:Fractional}(mapi::ClampMinMax{T,F}, val::F) = convert(N0f8,min(max(val, mapi.min), mapi.max)) map1{CT<:Colorant,F<:Fractional}(mapi::ClampMin{CT,F}, val::F) = convert(eltype(CT), max(val, mapi.min)) map1{CT<:Colorant,F<:Fractional}(mapi::ClampMax{CT,F}, val::F) = convert(eltype(CT), min(val, mapi.max)) map1{CT<:Colorant,F<:Fractional}(mapi::ClampMinMax{CT,F}, val::F) = convert(eltype(CT), min(max(val, mapi.min), mapi.max)) @@ -302,7 +297,7 @@ end function map1{To<:Union{RGB24,ARGB32},From<:Real}(mapi::ScaleMinMax{To,From}, val::From) t = clamp(val, mapi.min, mapi.max) f = mapi.s*t - mapi.s*mapi.min - convert(UFixed8, f) + convert(N0f8, f) end function map1{To<:Colorant,From<:Real}(mapi::ScaleMinMax{To,From}, val::From) t = clamp(val, mapi.min, mapi.max) @@ -344,8 +339,8 @@ similar{T,To,S}(mapi::ScaleSigned{To,S}, ::Type{T}, ::Type) = ScaleSigned{T,S}(m immap{T}(mapi::ScaleSigned{T}, val::Real) = convert(T, clamppm(mapi.s*val)) function immap{C<:AbstractRGB}(mapi::ScaleSigned{C}, val::Real) x = clamppm(mapi.s*val) - g = UFixed8(abs(x)) - ifelse(x >= 0, C(g, zero(UFixed8), g), C(zero(UFixed8), g, zero(UFixed8))) + g = N0f8(abs(x)) + ifelse(x >= 0, C(g, zero(N0f8), g), C(zero(N0f8), g, zero(N0f8))) end clamppm(x::Real) = ifelse(x >= 0, min(x, one(x)), max(x, -one(x))) @@ -366,7 +361,7 @@ immutable ScaleAutoMinMax{T} <: MapInfo{T} end end ScaleAutoMinMax{T}(::Type{T}) = ScaleAutoMinMax{T}() -ScaleAutoMinMax() = ScaleAutoMinMax{UFixed8}() +ScaleAutoMinMax() = ScaleAutoMinMax{N0f8}() similar{T}(mapi::ScaleAutoMinMax, ::Type{T}, ::Type) = ScaleAutoMinMax{T}() @@ -432,11 +427,11 @@ for SI in (MapInfo, AbstractClamp) @eval begin # Grayscale and GrayAlpha inputs immap(mapi::$ST{RGB24}, g::Gray) = immap(mapi, g.val) - immap(mapi::$ST{RGB24}, g::Real) = (x = map1(mapi, g); convert(RGB24, RGB{UFixed8}(x,x,x))) + immap(mapi::$ST{RGB24}, g::Real) = (x = map1(mapi, g); convert(RGB24, RGB{N0f8}(x,x,x))) function immap(mapi::$ST{RGB24}, g::AbstractFloat) if isfinite(g) x = map1(mapi, g) - convert(RGB24, RGB{UFixed8}(x,x,x)) + convert(RGB24, RGB{N0f8}(x,x,x)) else RGB24(0) end @@ -445,11 +440,11 @@ for SI in (MapInfo, AbstractClamp) immap(mapi::$ST{ARGB32}, g::Gray) = immap(mapi, g.val) function immap(mapi::$ST{ARGB32}, g::Real) x = map1(mapi, g) - convert(ARGB32, ARGB{UFixed8}(x,x,x,0xffuf8)) + convert(ARGB32, ARGB{N0f8}(x,x,x,0xffuf8)) end function immap{G<:Gray}(mapi::$ST{ARGB32}, g::TransparentColor{G}) x = map1(mapi, gray(g)) - convert(ARGB32, ARGB{UFixed8}(x,x,x,map1(mapi, g.alpha))) + convert(ARGB32, ARGB{N0f8}(x,x,x,map1(mapi, g.alpha))) end end for O in (:RGB, :BGR) @@ -480,15 +475,15 @@ for SI in (MapInfo, AbstractClamp) @eval begin # AbstractRGB and abstract ARGB inputs immap(mapi::$ST{RGB24}, rgb::AbstractRGB) = - convert(RGB24, RGB{UFixed8}(map1(mapi, red(rgb)), map1(mapi, green(rgb)), map1(mapi, blue(rgb)))) + convert(RGB24, RGB{N0f8}(map1(mapi, red(rgb)), map1(mapi, green(rgb)), map1(mapi, blue(rgb)))) immap{C<:AbstractRGB, TC}(mapi::$ST{RGB24}, argb::TransparentColor{C,TC}) = - convert(RGB24, RGB{UFixed8}(map1(mapi, red(argb)), map1(mapi, green(argb)), + convert(RGB24, RGB{N0f8}(map1(mapi, red(argb)), map1(mapi, green(argb)), map1(mapi, blue(argb)))) immap{C<:AbstractRGB, TC}(mapi::$ST{ARGB32}, argb::TransparentColor{C,TC}) = - convert(ARGB32, ARGB{UFixed8}(map1(mapi, red(argb)), map1(mapi, green(argb)), + convert(ARGB32, ARGB{N0f8}(map1(mapi, red(argb)), map1(mapi, green(argb)), map1(mapi, blue(argb)), map1(mapi, alpha(argb)))) immap(mapi::$ST{ARGB32}, rgb::AbstractRGB) = - convert(ARGB32, ARGB{UFixed8}(map1(mapi, red(rgb)), map1(mapi, green(rgb)), map1(mapi, blue(rgb)))) + convert(ARGB32, ARGB{N0f8}(map1(mapi, red(rgb)), map1(mapi, green(rgb)), map1(mapi, blue(rgb)))) end for O in (:RGB, :BGR) @eval begin @@ -503,10 +498,10 @@ for SI in (MapInfo, AbstractClamp) immap{T, C<:AbstractRGB, TC}(mapi::$ST{$OA{T}}, argb::TransparentColor{C,TC}) = $OA{T}(map1(mapi, red(argb)), map1(mapi, green(argb)), map1(mapi, blue(argb)), map1(mapi, alpha(argb))) - immap{T}(mapi::$ST{$OA{T}}, argb::ARGB32) = immap(mapi, convert(RGBA{UFixed8}, argb)) + immap{T}(mapi::$ST{$OA{T}}, argb::ARGB32) = immap(mapi, convert(RGBA{N0f8}, argb)) immap{T}(mapi::$ST{$OA{T}}, rgb::AbstractRGB) = $OA{T}(map1(mapi, red(rgb)), map1(mapi, green(rgb)), map1(mapi, blue(rgb))) - immap{T}(mapi::$ST{$OA{T}}, rgb::RGB24) = immap(mapi, convert(RGB{UFixed8}, argb)) + immap{T}(mapi::$ST{$OA{T}}, rgb::RGB24) = immap(mapi, convert(RGB{N0f8}, argb)) end end end @@ -580,7 +575,7 @@ end # Each "client" can define its own methods. "clients" include UFixed, # RGB24/ARGB32, and ImageMagick -const bitshiftto8 = ((UFixed10, 2), (UFixed12, 4), (UFixed14, 6), (UFixed16, 8)) +const bitshiftto8 = ((N6f10, 2), (N4f12, 4), (N2f14, 6), (N0f16, 8)) # typealias GrayType{T<:Fractional} Union{T, Gray{T}} typealias GrayArray{T<:Union{Fractional,Bool}} Union{AbstractArray{T}, AbstractArray{Gray{T}}} @@ -603,14 +598,14 @@ mapinfo{T<:UFixed}(::Type{T}, img::AbstractArray{T}) = MapNone(img) mapinfo{T<:AbstractFloat}(::Type{T}, img::AbstractArray{T}) = MapNone(img) # Grayscale methods -mapinfo(::Type{UFixed8}, img::GrayArray{Bool}) = MapNone{UFixed8}() -mapinfo(::Type{UFixed8}, img::GrayArray{UFixed8}) = MapNone{UFixed8}() -mapinfo(::Type{Gray{UFixed8}}, img::GrayArray{UFixed8}) = MapNone{Gray{UFixed8}}() -mapinfo(::Type{GrayA{UFixed8}}, img::AbstractArray{GrayA{UFixed8}}) = MapNone{GrayA{UFixed8}}() +mapinfo(::Type{N0f8}, img::GrayArray{Bool}) = MapNone{N0f8}() +mapinfo(::Type{N0f8}, img::GrayArray{N0f8}) = MapNone{N0f8}() +mapinfo(::Type{Gray{N0f8}}, img::GrayArray{N0f8}) = MapNone{Gray{N0f8}}() +mapinfo(::Type{GrayA{N0f8}}, img::AbstractArray{GrayA{N0f8}}) = MapNone{GrayA{N0f8}}() for (T,n) in bitshiftto8 - @eval mapinfo(::Type{UFixed8}, img::GrayArray{$T}) = BitShift{UFixed8,$n}() - @eval mapinfo(::Type{Gray{UFixed8}}, img::GrayArray{$T}) = BitShift{Gray{UFixed8},$n}() - @eval mapinfo(::Type{GrayA{UFixed8}}, img::AbstractArray{GrayA{$T}}) = BitShift{GrayA{UFixed8},$n}() + @eval mapinfo(::Type{N0f8}, img::GrayArray{$T}) = BitShift{N0f8,$n}() + @eval mapinfo(::Type{Gray{N0f8}}, img::GrayArray{$T}) = BitShift{Gray{N0f8},$n}() + @eval mapinfo(::Type{GrayA{N0f8}}, img::AbstractArray{GrayA{$T}}) = BitShift{GrayA{N0f8},$n}() end mapinfo{T<:UFixed,F<:AbstractFloat}(::Type{T}, img::GrayArray{F}) = ClampMinMax(T, zero(F), one(F)) mapinfo{T<:UFixed,F<:AbstractFloat}(::Type{Gray{T}}, img::GrayArray{F}) = ClampMinMax(Gray{T}, zero(F), one(F)) @@ -622,14 +617,14 @@ mapinfo{F<:Fractional}(::Type{RGB24}, img::GrayArray{F}) = ClampMinMax(RGB24, ze mapinfo{F<:Fractional}(::Type{ARGB32}, img::AbstractArray{F}) = ClampMinMax(ARGB32, zero(F), one(F)) # Color->Color methods -mapinfo(::Type{RGB{UFixed8}}, img) = MapNone{RGB{UFixed8}}() -mapinfo(::Type{RGBA{UFixed8}}, img) = MapNone{RGBA{UFixed8}}() +mapinfo(::Type{RGB{N0f8}}, img) = MapNone{RGB{N0f8}}() +mapinfo(::Type{RGBA{N0f8}}, img) = MapNone{RGBA{N0f8}}() for (T,n) in bitshiftto8 - @eval mapinfo(::Type{RGB{UFixed8}}, img::AbstractArray{RGB{$T}}) = BitShift{RGB{UFixed8},$n}() - @eval mapinfo(::Type{RGBA{UFixed8}}, img::AbstractArray{RGBA{$T}}) = BitShift{RGBA{UFixed8},$n}() + @eval mapinfo(::Type{RGB{N0f8}}, img::AbstractArray{RGB{$T}}) = BitShift{RGB{N0f8},$n}() + @eval mapinfo(::Type{RGBA{N0f8}}, img::AbstractArray{RGBA{$T}}) = BitShift{RGBA{N0f8},$n}() end -mapinfo{F<:Fractional}(::Type{RGB{UFixed8}}, img::AbstractArray{RGB{F}}) = Clamp(RGB{UFixed8}) -mapinfo{F<:Fractional}(::Type{RGBA{UFixed8}}, img::AbstractArray{RGBA{F}}) = Clamp(RGBA{UFixed8}) +mapinfo{F<:Fractional}(::Type{RGB{N0f8}}, img::AbstractArray{RGB{F}}) = Clamp(RGB{N0f8}) +mapinfo{F<:Fractional}(::Type{RGBA{N0f8}}, img::AbstractArray{RGBA{F}}) = Clamp(RGBA{N0f8}) @@ -638,8 +633,8 @@ mapinfo(::Type{RGB24}, img::AbstractArray{RGB24}) = MapNone{RGB24}() mapinfo(::Type{ARGB32}, img::AbstractArray{ARGB32}) = MapNone{ARGB32}() for C in tuple(subtypes(AbstractRGB)..., Gray) C == RGB24 && continue - @eval mapinfo(::Type{RGB24}, img::AbstractArray{$C{UFixed8}}) = MapNone{RGB24}() - @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$C{UFixed8}}) = MapNone{ARGB32}() + @eval mapinfo(::Type{RGB24}, img::AbstractArray{$C{N0f8}}) = MapNone{RGB24}() + @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$C{N0f8}}) = MapNone{ARGB32}() for (T, n) in bitshiftto8 @eval mapinfo(::Type{RGB24}, img::AbstractArray{$C{$T}}) = BitShift{RGB24, $n}() @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$C{$T}}) = BitShift{ARGB32, $n}() @@ -648,8 +643,8 @@ for C in tuple(subtypes(AbstractRGB)..., Gray) @eval mapinfo{F<:AbstractFloat}(::Type{ARGB32}, img::AbstractArray{$C{F}}) = ClampMinMax(ARGB32, zero(F), one(F)) for AC in subtypes(TransparentColor) length(AC.parameters) == 2 || continue - @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$AC{$C{UFixed8},UFixed8}}) = MapNone{ARGB32}() - @eval mapinfo(::Type{RGB24}, img::AbstractArray{$AC{$C{UFixed8},UFixed8}}) = MapNone{RGB24}() + @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$AC{$C{N0f8},N0f8}}) = MapNone{ARGB32}() + @eval mapinfo(::Type{RGB24}, img::AbstractArray{$AC{$C{N0f8},N0f8}}) = MapNone{RGB24}() for (T, n) in bitshiftto8 @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$AC{$C{$T},$T}}) = BitShift{ARGB32, $n}() @eval mapinfo(::Type{RGB24}, img::AbstractArray{$AC{$C{$T},$T}}) = BitShift{RGB24, $n}() @@ -674,14 +669,14 @@ mapinfo(::Type{UInt32}, img::AbstractArray{UInt32}) = MapNone{UInt32}() # Clamping mapinfo client. Converts to RGB and uses UFixed, clamping # floating-point values to [0,1]. mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{T}) = MapNone{T}() -mapinfo{T<:AbstractFloat}(::Type{Clamp}, img::AbstractArray{T}) = ClampMinMax(UFixed8, zero(T), one(T)) +mapinfo{T<:AbstractFloat}(::Type{Clamp}, img::AbstractArray{T}) = ClampMinMax(N0f8, zero(T), one(T)) let handled = Set() for ACV in (Color, AbstractRGB) for CV in subtypes(ACV) (length(CV.parameters) == 1 && !(CV.abstract)) || continue CVnew = CV<:AbstractGray ? Gray : RGB @eval mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$CV{T}}) = MapNone{$CVnew{T}}() - @eval mapinfo{CV<:$CV}(::Type{Clamp}, img::AbstractArray{CV}) = Clamp{$CVnew{UFixed8}}() + @eval mapinfo{CV<:$CV}(::Type{Clamp}, img::AbstractArray{CV}) = Clamp{$CVnew{N0f8}}() CVnew = CV<:AbstractGray ? Gray : BGR AC, CA = alphacolor(CV), coloralpha(CV) if AC in handled @@ -691,15 +686,15 @@ for ACV in (Color, AbstractRGB) ACnew, CAnew = alphacolor(CVnew), coloralpha(CVnew) @eval begin mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$AC{T}}) = MapNone{$ACnew{T}}() - mapinfo{P<:$AC}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$ACnew{UFixed8}}() + mapinfo{P<:$AC}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$ACnew{N0f8}}() mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$CA{T}}) = MapNone{$CAnew{T}}() - mapinfo{P<:$CA}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$CAnew{UFixed8}}() + mapinfo{P<:$CA}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$CAnew{N0f8}}() end end end end -mapinfo(::Type{Clamp}, img::AbstractArray{RGB24}) = MapNone{RGB{UFixed8}}() -mapinfo(::Type{Clamp}, img::AbstractArray{ARGB32}) = MapNone{BGRA{UFixed8}}() +mapinfo(::Type{Clamp}, img::AbstractArray{RGB24}) = MapNone{RGB{N0f8}}() +mapinfo(::Type{Clamp}, img::AbstractArray{ARGB32}) = MapNone{BGRA{N0f8}}() """ @@ -710,8 +705,8 @@ imgsc = sc(img, min, max) Applies default or specified `ScaleMinMax` mapping to the image. """ -sc(img::AbstractArray) = immap(ScaleMinMax(UFixed8, img), img) -sc(img::AbstractArray, mn::Real, mx::Real) = immap(ScaleMinMax(UFixed8, img, mn, mx), img) +sc(img::AbstractArray) = immap(ScaleMinMax(N0f8, img), img) +sc(img::AbstractArray, mn::Real, mx::Real) = immap(ScaleMinMax(N0f8, img, mn, mx), img) ufixedsc{T<:UFixed}(::Type{T}, img::AbstractArray) = immap(mapinfo(T, img), img) -ufixed8sc(img::AbstractArray) = ufixedsc(UFixed8, img) +ufixed8sc(img::AbstractArray) = ufixedsc(N0f8, img) diff --git a/src/showmime.jl b/src/showmime.jl index 2b68b342..c5506044 100644 --- a/src/showmime.jl +++ b/src/showmime.jl @@ -32,5 +32,5 @@ function Base.show{C<:Colorant}(io::IO, mime::MIME"image/png", img::AbstractMatr end # Jupyter seemingly can't handle 16-bit colors -map8(c::Colorant) = mapc(UFixed8, c) -map8(x::Number) = UFixed8(x) +map8(c::Colorant) = mapc(N0f8, c) +map8(x::Number) = N0f8(x) diff --git a/test/old/algorithms.jl b/test/old/algorithms.jl index ed7a46a5..a16d4fa3 100644 --- a/test/old/algorithms.jl +++ b/test/old/algorithms.jl @@ -42,19 +42,19 @@ facts("Algorithms") do @fact all(A.*img2 .== fill(RGB{Float32}(1,1,1), 4, 5)) --> true "test OT1P7V" img2 = img2 .- RGB{Float32}(1,1,1)/2 A = rand(UInt8,3,4) - img = reinterpret(Gray{UFixed8}, Images.grayim(A)) + img = reinterpret(Gray{N0f8}, Images.grayim(A)) imgm = mean(img) imgn = img/imgm @fact reinterpret(Float64, Images.data(imgn)) --> roughly(convert(Array{Float64}, A/mean(A))) "test NTAvmj" @fact imcomplement([Gray(0.2)]) --> [Gray(0.8)] "test Gu9b6h" - @fact imcomplement([Gray{U8}(0.2)]) --> [Gray{U8}(0.8)] "test Kle9oP" + @fact imcomplement([Gray{N0f8}(0.2)]) --> [Gray{N0f8}(0.8)] "test Kle9oP" @fact imcomplement([RGB(0,0.3,1)]) --> [RGB(1,0.7,0)] "test jgxQxd" @fact imcomplement([RGBA(0,0.3,1,0.7)]) --> [RGBA(1.0,0.7,0.0,0.7)] "test tEAo1x" - @fact imcomplement([RGBA{U8}(0,0.6,1,0.7)]) --> [RGBA{U8}(1.0,0.4,0.0,0.7)] "test MGiodE" + @fact imcomplement([RGBA{N0f8}(0,0.6,1,0.7)]) --> [RGBA{N0f8}(1.0,0.4,0.0,0.7)] "test MGiodE" img = rand(1:10,10,10) img2 = rand(1:2,10,10) - img3 = reinterpret(Gray{U8}, grayim(rand(UInt8,10,10))) + img3 = reinterpret(Gray{N0f8}, grayim(rand(UInt8,10,10))) @fact all([entropy(img, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test lwgD8l" @fact all([entropy(img2, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test L7wFfa" @fact all([entropy(img3, kind=kind) for kind in [:shannon,:nat,:hartley]] .≥ 0) --> true "test OHwUzO" @@ -80,7 +80,7 @@ facts("Algorithms") do A = rand(10:20, 5, 5) @fact minfinite(A) --> minimum(A) "test k5GtIe" @fact maxfinite(A) --> maximum(A) "test 8qaTAa" - A = reinterpret(UFixed8, rand(0x00:0xff, 5, 5)) + A = reinterpret(N0f8, rand(0x00:0xff, 5, 5)) @fact minfinite(A) --> minimum(A) "test RCl2VS" @fact maxfinite(A) --> maximum(A) "test eKwX2u" A = rand(Float32,3,5,5) @@ -96,16 +96,16 @@ facts("Algorithms") do b = convert(Array{UInt8}, [134, 252, 4]) @fact Images.sad(a, b) --> 387 "test sx70B8" @fact Images.ssd(a, b) --> 80699 "test aFz7hO" - af = reinterpret(UFixed8, a) - bf = reinterpret(UFixed8, b) + af = reinterpret(N0f8, a) + bf = reinterpret(N0f8, b) @fact Images.sad(af, bf) --> roughly(387f0/255) "test R3U9a6" @fact Images.ssd(af, bf) --> roughly(80699f0/255^2) "test WtPNxa" - ac = reinterpret(RGB{UFixed8}, a) - bc = reinterpret(RGB{UFixed8}, b) + ac = reinterpret(RGB{N0f8}, a) + bc = reinterpret(RGB{N0f8}, b) @fact Images.sad(ac, bc) --> roughly(387f0/255) "test wtRHsd" @fact Images.ssd(ac, bc) --> roughly(80699f0/255^2) "test Ti1QN0" - ag = reinterpret(RGB{UFixed8}, a) - bg = reinterpret(RGB{UFixed8}, b) + ag = reinterpret(RGB{N0f8}, a) + bg = reinterpret(RGB{N0f8}, b) @fact Images.sad(ag, bg) --> roughly(387f0/255) "test jaMtWn" @fact Images.ssd(ag, bg) --> roughly(80699f0/255^2) "test Gc9gbr" @@ -227,13 +227,13 @@ facts("Algorithms") do 32.875 50.5 42.875; 16.90625 25.875 21.90625]) @fact B --> roughly(Btarget) "test g0lXjp" - Argb = reinterpret(RGB, reinterpret(UFixed16, permutedims(A, (3,1,2)))) + Argb = reinterpret(RGB, reinterpret(N0f16, permutedims(A, (3,1,2)))) B = Images.restrict(Argb) Bf = permutedims(reinterpret(eltype(eltype(B)), B), (2,3,1)) - @fact Bf --> roughly(Btarget/reinterpret(one(UFixed16)), 1e-12) "test IVByaq" - Argba = reinterpret(RGBA{UFixed16}, reinterpret(UFixed16, A)) + @fact Bf --> roughly(Btarget/reinterpret(one(N0f16)), 1e-12) "test IVByaq" + Argba = reinterpret(RGBA{N0f16}, reinterpret(N0f16, A)) B = Images.restrict(Argba) - @fact reinterpret(eltype(eltype(B)), B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(UFixed16)), 1e-12) "test z8K24e" + @fact reinterpret(eltype(eltype(B)), B) --> roughly(Images.restrict(A, (2,3))/reinterpret(one(N0f16)), 1e-12) "test z8K24e" A = reshape(1:60, 5, 4, 3) B = Images.restrict(A, (1,2,3)) @fact cat(3, [ 2.6015625 8.71875 6.1171875; @@ -250,7 +250,7 @@ facts("Algorithms") do @inferred(restrict(imgcolax, Axis{:x})) # Issue #395 img1 = colorim(fill(0.9, 3, 5, 5)) - img2 = colorim(fill(U8(0.9), 3, 5, 5)) + img2 = colorim(fill(N0f8(0.9), 3, 5, 5)) @fact separate(restrict(img1)) --> roughly(separate(restrict(img2)), 0.01) "test TH8OoL" end diff --git a/test/old/core.jl b/test/old/core.jl index 64a7e678..10bc115e 100644 --- a/test/old/core.jl +++ b/test/old/core.jl @@ -16,7 +16,7 @@ facts("Core") do B = rand(UInt16(1):UInt16(20), 3, 5) # img, imgd, and imgds will be used in many more tests # Thus, these must be defined as local if reassigned in any context() blocks - cmap = reinterpret(RGB, repmat(reinterpret(UFixed8, round(UInt8, linspace(12, 255, 20)))', 3, 1)) + cmap = reinterpret(RGB, repmat(reinterpret(N0f8, round(UInt8, linspace(12, 255, 20)))', 3, 1)) imgi = IndirectArray(copy(B), cmap) img = AxisArray(imgi, Axis{:y}(2*(1:size(imgi,1))), Axis{:x}(3*(1:size(imgi,2)))) imgd0 = convert(Array, imgi) @@ -40,19 +40,19 @@ facts("Core") do @fact colordim(B) --> 0 "test HAdcG0" @fact grayim(img) --> img "test mJppbD" Bf = grayim(round(UInt8, B)) - @fact eltype(Bf) --> Gray{UFixed8} "test luvkCN" + @fact eltype(Bf) --> Gray{N0f8} "test luvkCN" @fact colorspace(Bf) --> "Gray" "test JwE99B" @fact colordim(Bf) --> 0 "test DNzikz" Bf = grayim(B) - @fact eltype(Bf) --> Gray{UFixed16} "test CPd4Fa" + @fact eltype(Bf) --> Gray{N0f16} "test CPd4Fa" # colorspace encoded as a Color (enables multiple dispatch) - BfCV = reinterpret(Gray{UFixed8}, round(UInt8, B)) + BfCV = reinterpret(Gray{N0f8}, round(UInt8, B)) @fact colorspace(BfCV) --> "Gray" "test Tp3xdg" @fact colordim(BfCV) --> 0 "test aALUCW" Bf3 = grayim(reshape(collect(convert(UInt8,1):convert(UInt8,36)), 3,4,3)) - @fact eltype(Bf3) --> Gray{UFixed8} "test k8hBgR" + @fact eltype(Bf3) --> Gray{N0f8} "test k8hBgR" Bf3 = grayim(reshape(collect(convert(UInt16,1):convert(UInt16,36)), 3,4,3)) - @fact eltype(Bf3) --> Gray{UFixed16} "test KASRod" + @fact eltype(Bf3) --> Gray{N0f16} "test KASRod" Bf3 = grayim(reshape(collect(1.0f0:36.0f0), 3,4,3)) @fact eltype(Bf3) --> Gray{Float32} "test Vr9iDW" end @@ -60,22 +60,22 @@ facts("Core") do context("Colorim") do C = colorim(rand(UInt8, 3, 5, 5)) - @fact eltype(C) --> RGB{UFixed8} "test N0iGXo" + @fact eltype(C) --> RGB{N0f8} "test N0iGXo" @fact colordim(C) --> 0 "test 6COaeI" @fact colorim(C) --> C "test hPgSHp" C = colorim(rand(UInt16, 4, 5, 5), "ARGB") - @fact eltype(C) --> ARGB{UFixed16} "test PZ5Ld5" + @fact eltype(C) --> ARGB{N0f16} "test PZ5Ld5" C = colorim(rand(UInt8(1):UInt8(20), 3, 5, 5)) - @fact eltype(C) --> RGB{U8} "test MyDpnz" + @fact eltype(C) --> RGB{N0f8} "test MyDpnz" @fact colordim(C) --> 0 "test vu1pXs" @fact colorspace(C) --> "RGB" "test moBy0x" - @fact eltype(colorim(rand(UInt16, 3, 5, 5))) --> RGB{UFixed16} "test pzSy1a" + @fact eltype(colorim(rand(UInt16, 3, 5, 5))) --> RGB{N0f16} "test pzSy1a" @fact eltype(colorim(rand(3, 5, 5))) --> RGB{Float64} "test F3ex39" @fact colordim(colorim(rand(UInt8, 5, 5, 3))) --> 0 "test kElkKH" @fact spatialorder(colorim(rand(UInt8, 3, 5, 5))) --> (:y, :x) "test auwSni" @fact spatialorder(colorim(rand(UInt8, 5, 5, 3))) --> (:y, :x) "test g307S1" - @fact eltype(colorim(rand(UInt8, 4, 5, 5), "RGBA")) --> RGBA{UFixed8} "test RcSRnh" - @fact eltype(colorim(rand(UInt8, 4, 5, 5), "ARGB")) --> ARGB{UFixed8} "test 9hscsz" + @fact eltype(colorim(rand(UInt8, 4, 5, 5), "RGBA")) --> RGBA{N0f8} "test RcSRnh" + @fact eltype(colorim(rand(UInt8, 4, 5, 5), "ARGB")) --> ARGB{N0f8} "test 9hscsz" @fact colordim(colorim(rand(UInt8, 5, 5, 4), "RGBA")) --> 0 "test SJTuxu" @fact colordim(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> 0 "test lIpIVN" @fact spatialorder(colorim(rand(UInt8, 5, 5, 4), "ARGB")) --> (:y, :x) "test y0Vpv4" @@ -95,8 +95,8 @@ facts("Core") do @fact colorspace(img_) --> "RGB" "test F3LOhS" # Note: img from opening of facts() block # TODO: refactor whole block - @fact eltype(img) --> RGB{UFixed8} "test 8aSK8W" - @fact eltype(imgd) --> RGB{UFixed8} "test IhyuSH" + @fact eltype(img) --> RGB{N0f8} "test 8aSK8W" + @fact eltype(imgd) --> RGB{N0f8} "test IhyuSH" end end @@ -240,7 +240,7 @@ facts("Core") do @fact ndims(s) --> 3 "test Kxj25e" @fact sdims(s) --> 2 "test L1qDs3" @fact colordim(s) --> 3 "test BGZevb" - @fact colorspace(s) --> "Gray" "test U8NVOG" + @fact colorspace(s) --> "Gray" "test N0f8NVOG" s = getindexim(imgds, 2:2, 1:4, 2) @fact ndims(s) --> 2 "test nKN91R" @fact sdims(s) --> 2 "test SFKKhD" @@ -328,48 +328,48 @@ facts("Core") do @fact anew --> a "test VU6f3n" @fact_throws DimensionMismatch reinterpret(RGB{Float32}, af) "test 86GKXq" Au8 = rand(0x00:0xff, 3, 5, 4) - A8 = reinterpret(UFixed8, Au8) + A8 = reinterpret(N0f8, Au8) rawrgb8 = reinterpret(RGB, A8) - @fact eltype(rawrgb8) --> RGB{UFixed8} "test U5YnpG" - @fact reinterpret(UFixed8, rawrgb8) --> A8 "test VIsSBT" + @fact eltype(rawrgb8) --> RGB{N0f8} "test U5YnpG" + @fact reinterpret(N0f8, rawrgb8) --> A8 "test VIsSBT" @fact reinterpret(UInt8, rawrgb8) --> Au8 "test cWVSZ5" rawrgb32 = convert(Array{RGB{Float32}}, rawrgb8) @fact eltype(rawrgb32) --> RGB{Float32} "test BPX0oN" @fact ufixed8(rawrgb32) --> rawrgb8 "test 10hrUB" - @fact reinterpret(UFixed8, rawrgb8) --> A8 "test xz6V7Y" + @fact reinterpret(N0f8, rawrgb8) --> A8 "test xz6V7Y" imrgb8 = convert(Image, rawrgb8) @fact spatialorder(imrgb8) --> (Symbol.(Images.yx)...,) "test up03c2" @fact convert(Image, imrgb8) --> exactly(imrgb8) "test CaRwd8" - @fact convert(Image{RGB{UFixed8}}, imrgb8) --> exactly(imrgb8) "test 39AZU3" - im8 = reinterpret(UFixed8, imrgb8) + @fact convert(Image{RGB{N0f8}}, imrgb8) --> exactly(imrgb8) "test 39AZU3" + im8 = reinterpret(N0f8, imrgb8) @fact data(im8) --> A8 "test lZSAH9" - @fact permutedims(ufixedview(U8, separate(imrgb8)), (3, 1, 2)) --> im8 "test zDOWZM" + @fact permutedims(ufixedview(N0f8, separate(imrgb8)), (3, 1, 2)) --> im8 "test zDOWZM" @fact reinterpret(UInt8, imrgb8) --> Au8 "test HeezpR" @fact reinterpret(RGB, im8) --> imrgb8 "test VJUpj3" ims8 = separate(imrgb8) @fact colordim(ims8) --> 0 "test nGifan" @fact colorspace(ims8) --> "Gray" "test R0VFeL" @fact convert(Image, ims8) --> exactly(ims8) "test EGoCYN" - @fact convert(Image{UFixed8}, ims8) --> exactly(ims8) "test Qly190" + @fact convert(Image{N0f8}, ims8) --> exactly(ims8) "test Qly190" @fact separate(ims8) --> exactly(ims8) "test hAxqus" - A = reinterpret(UFixed8, UInt8[1 2; 3 4]) - imgray = convert(Image{Gray{UFixed8}}, A) + A = reinterpret(N0f8, UInt8[1 2; 3 4]) + imgray = convert(Image{Gray{N0f8}}, A) @fact spatialorder(imgray) --> (Symbol.(Images.yx)...,) "test gLMUOh" - @fact data(imgray) --> reinterpret(Gray{UFixed8}, [0x01 0x02; 0x03 0x04]) "test UC9NtZ" + @fact data(imgray) --> reinterpret(Gray{N0f8}, [0x01 0x02; 0x03 0x04]) "test UC9NtZ" @fact eltype(convert(Image{HSV{Float32}}, imrgb8)) --> HSV{Float32} "test cwCaVn" @fact eltype(convert(Image{HSV}, float32(imrgb8))) --> HSV{Float32} "test VB4EU3" - @fact eltype(convert(Array{Gray}, imrgb8)) --> Gray{U8} "test 4UOxZh" - @fact eltype(convert(Image{Gray}, imrgb8)) --> Gray{U8} "test 2hhcQd" - @fact eltype(convert(Array{Gray}, data(imrgb8))) --> Gray{U8} "test SdEY95" - @fact eltype(convert(Image{Gray}, data(imrgb8))) --> Gray{U8} "test lzipLL" + @fact eltype(convert(Array{Gray}, imrgb8)) --> Gray{N0f8} "test 4UOxZh" + @fact eltype(convert(Image{Gray}, imrgb8)) --> Gray{N0f8} "test 2hhcQd" + @fact eltype(convert(Array{Gray}, data(imrgb8))) --> Gray{N0f8} "test SdEY95" + @fact eltype(convert(Image{Gray}, data(imrgb8))) --> Gray{N0f8} "test lzipLL" # Issue 232 - let img = Image(reinterpret(Gray{UFixed16}, rand(UInt16, 5, 5))) + let img = Image(reinterpret(Gray{N0f16}, rand(UInt16, 5, 5))) imgs = subim(img, :, :) - @fact isa(minfinite(imgs), Gray{UFixed16}) --> true "test PlxHep" + @fact isa(minfinite(imgs), Gray{N0f16}) --> true "test PlxHep" # Raw imgdata = rand(UInt16, 5, 5) - img = Image(reinterpret(Gray{UFixed16}, imgdata)) + img = Image(reinterpret(Gray{N0f16}, imgdata)) @fact all(raw(img) .== imgdata) --> true "test EvOATF" @fact typeof(raw(img).data) --> Array{UInt16,2} "test YlySCh" @fact typeof(raw(Image(rawrgb8)).data) --> Array{UInt8,3} "test uOxsmv" # check color images diff --git a/test/old/edge.jl b/test/old/edge.jl index 8f68b3b5..6269ccf3 100644 --- a/test/old/edge.jl +++ b/test/old/edge.jl @@ -81,7 +81,7 @@ facts("Edge") do @fact all(edges[nondiags] .== 0.0) --> true "test WDQfYZ" #Checks Hysteresis Thresholding - img = ones(Gray{U8}, (10, 10)) + img = ones(Gray{N0f8}, (10, 10)) img[3:7, 3:7] = 0.0 img[4:6, 4:6] = 0.7 thresholded = Images.hysteresis_thresholding(img, 0.9, 0.8) @@ -403,4 +403,3 @@ end end end -end diff --git a/test/old/exposure.jl b/test/old/exposure.jl index 32aaf5fe..aab281ba 100644 --- a/test/old/exposure.jl +++ b/test/old/exposure.jl @@ -28,11 +28,11 @@ facts("Exposure") do @fact hist --> [25, 5, 5, 5, 5, 5, 5, 5, 40] # Test the more typical case - img = reinterpret(Gray{U8}, [0x20,0x40,0x80,0xd0]) + img = reinterpret(Gray{N0f8}, [0x20,0x40,0x80,0xd0]) @fact imhist(img, 5) --> (0.0:0.2:1.0,[0,1,1,1,0,1,0]) - img = reinterpret(Gray{U8}, [0x00,0x40,0x80,0xd0]) + img = reinterpret(Gray{N0f8}, [0x00,0x40,0x80,0xd0]) @fact imhist(img, 5) --> (0.0:0.2:1.0,[0,1,1,1,0,1,0]) - img = reinterpret(Gray{U8}, [0x00,0x40,0x80,0xff]) + img = reinterpret(Gray{N0f8}, [0x00,0x40,0x80,0xff]) @fact imhist(img, 6) --> (0.0:0.2:1.2,[0,1,1,1,0,0,1,0]) end @@ -45,26 +45,26 @@ facts("Exposure") do @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U8}, 10, 10) + img = ones(Images.Gray{Images.N0f8}, 10, 10) ret = histeq(img, 100) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U16}, 10, 10) + img = ones(Images.Gray{Images.N0f16}, 10, 10) ret = histeq(img, 100) @fact eltype(ret) == eltype(img) --> true - img = ones(Images.AGray{Images.U8}, 10, 10) + img = ones(Images.AGray{Images.N0f8}, 10, 10) ret = histeq(img, 100) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U8}, 10, 10) + img = ones(Images.RGB{Images.N0f8}, 10, 10) ret = histeq(img, 100) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U16}, 10, 10) + img = ones(Images.RGB{Images.N0f16}, 10, 10) ret = histeq(img, 100) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true @@ -74,7 +74,7 @@ facts("Exposure") do @fact all(map((i, r) -> isapprox(i, r), img, ret)) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.ARGB{Images.U8}, 10, 10) + img = ones(Images.ARGB{Images.N0f8}, 10, 10) ret = histeq(img, 100) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true @@ -124,26 +124,26 @@ facts("Exposure") do @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U8}, 10, 10) + img = ones(Images.Gray{Images.N0f8}, 10, 10) ret = adjust_gamma(img, 1) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U16}, 10, 10) + img = ones(Images.Gray{Images.N0f16}, 10, 10) ret = adjust_gamma(img, 1) @fact eltype(ret) == eltype(img) --> true - img = ones(Images.AGray{Images.U8}, 10, 10) + img = ones(Images.AGray{Images.N0f8}, 10, 10) ret = adjust_gamma(img, 1) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U8}, 10, 10) + img = ones(Images.RGB{Images.N0f8}, 10, 10) ret = adjust_gamma(img, 1) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U16}, 10, 10) + img = ones(Images.RGB{Images.N0f16}, 10, 10) ret = adjust_gamma(img, 1) @fact img == ret --> true @fact eltype(ret) == eltype(img) --> true @@ -153,7 +153,7 @@ facts("Exposure") do @fact all(map((i, r) -> isapprox(i, r), img, ret)) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.ARGB{Images.U8}, 10, 10) + img = ones(Images.ARGB{Images.N0f8}, 10, 10) ret = adjust_gamma(img, 1) @fact img == ret --> true @@ -194,24 +194,24 @@ facts("Exposure") do @fact all(ret .== zero(eltype(img))) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U8}, 10, 10) + img = ones(Images.Gray{Images.N0f8}, 10, 10) ret = histmatch(img, img) @fact all(ret .== zero(eltype(img))) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U16}, 10, 10) + img = ones(Images.Gray{Images.N0f16}, 10, 10) ret = histmatch(img, img) @fact eltype(ret) == eltype(img) --> true - img = ones(Images.AGray{Images.U8}, 10, 10) + img = ones(Images.AGray{Images.N0f8}, 10, 10) ret = histmatch(img, img) @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U8}, 10, 10) + img = ones(Images.RGB{Images.N0f8}, 10, 10) ret = histmatch(img, img) @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U16}, 10, 10) + img = ones(Images.RGB{Images.N0f16}, 10, 10) ret = histmatch(img, img) @fact eltype(ret) == eltype(img) --> true @@ -220,7 +220,7 @@ facts("Exposure") do @fact all(map((i, r) -> isapprox(zero(RGB), r, atol = 0.001), img, ret)) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.ARGB{Images.U8}, 10, 10) + img = ones(Images.ARGB{Images.N0f8}, 10, 10) ret = histmatch(img, img) @fact eltype(ret) == eltype(img) --> true @@ -241,27 +241,27 @@ facts("Exposure") do @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U8}, 10, 10) + img = ones(Images.Gray{Images.N0f8}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.Gray{Images.U16}, 10, 10) + img = ones(Images.Gray{Images.N0f16}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.AGray{Images.U8}, 10, 10) + img = ones(Images.AGray{Images.N0f8}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U8}, 10, 10) + img = ones(Images.RGB{Images.N0f8}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.RGB{Images.U16}, 10, 10) + img = ones(Images.RGB{Images.N0f16}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true @@ -271,7 +271,7 @@ facts("Exposure") do @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true - img = ones(Images.ARGB{Images.U8}, 10, 10) + img = ones(Images.ARGB{Images.N0f8}, 10, 10) ret = clahe(img, 100) @fact size(ret) == size(img) --> true @fact eltype(ret) == eltype(img) --> true @@ -335,15 +335,15 @@ facts("Exposure") do context("Other") do # Issue #282 - img = convert(Images.Image{Gray{UFixed8}}, eye(2,2)) + img = convert(Images.Image{Gray{N0f8}}, eye(2,2)) imgs = Images.imstretch(img, 0.3, 0.4) @fact data(imgs) --> roughly(1./(1 + (0.3./(eye(2,2) + eps())).^0.4)) - img = convert(Images.Image{Gray{UFixed16}}, [0.01164 0.01118; 0.01036 0.01187]) + img = convert(Images.Image{Gray{N0f16}}, [0.01164 0.01118; 0.01036 0.01187]) @fact data(imadjustintensity(img,[0.0103761, 0.0252166]))[2,1] --> 0.0 - @fact eltype(imadjustintensity(img)) --> Gray{UFixed16} + @fact eltype(imadjustintensity(img)) --> Gray{N0f16} - img = convert(Images.Image{Gray{UFixed16}}, [0.01164 0.01118; 0.01036 0.01187]) + img = convert(Images.Image{Gray{N0f16}}, [0.01164 0.01118; 0.01036 0.01187]) @fact Images.complement(Gray(0.5)) == Gray(0.5) --> true @fact Images.complement(Gray(0.2)) == Gray(0.8) --> true @fact all(imcomplement(img) .== 1 - img) --> true diff --git a/test/old/map.jl b/test/old/map.jl index cd9e8373..721154a5 100644 --- a/test/old/map.jl +++ b/test/old/map.jl @@ -26,7 +26,7 @@ facts("Map") do @chk map(mapi, [0x07]) a7 @fact map(mapi, a7) --> exactly(a7) mapi = MapNone{RGB24}() - g = UFixed8(0.1) + g = N0f8(0.1) @chk Images.map1(mapi, 0.1) g @chk map(mapi, 0.1) RGB24(g,g,g) @chk map(mapi, Gray(0.1)) RGB24(g,g,g) @@ -43,9 +43,9 @@ facts("Map") do @chk map(mapi, c) convert(HSV, c) # issue #200 - c = RGBA{UFixed8}(1,0.5,0.25,0.8) - mapi = MapNone{Images.ColorTypes.BGRA{UFixed8}}() - @chk map(mapi, c) convert(Images.ColorTypes.BGRA{UFixed8}, c) + c = RGBA{N0f8}(1,0.5,0.25,0.8) + mapi = MapNone{Images.ColorTypes.BGRA{N0f8}}() + @chk map(mapi, c) convert(Images.ColorTypes.BGRA{N0f8}, c) end context("BitShift") do @@ -53,7 +53,7 @@ facts("Map") do @chk map(mapi, 0xff) 0x01 @chk map(mapi, 0xf0ff) 0xff @chk map(mapi, [0xff]) UInt8[0x01] - mapi = BitShift{UFixed8,7}() + mapi = BitShift{N0f8,7}() @chk map(mapi, 0xffuf8) 0x01uf8 @chk map(mapi, 0xf0ffuf16) 0xffuf8 mapi = BitShift{ARGB32,4}() @@ -61,24 +61,24 @@ facts("Map") do mapi = BitShift{RGB24,2}() @chk map(mapi, Gray(0xffuf8)) reinterpret(RGB24, 0x003f3f3f) mapi = BitShift{ARGB32,2}() - @chk map(mapi, Gray(0xffuf8)) reinterpret(ARGB32, 0xff3f3f3f) - @chk map(mapi, GrayA{UFixed8}(Gray(0xffuf8),0x3fuf8)) reinterpret(ARGB32, 0x0f3f3f3f) - mapi = BitShift{RGB{UFixed8},2}() + @chk map(mapi, Gray(0xffuf8)) ARGB32(0xff3f3f3f) + @chk map(mapi, GrayA{N0f8}(Gray(0xffuf8),0x3fuf8)) ARGB32(0x0f3f3f3f) + mapi = BitShift{RGB{N0f8},2}() @chk map(mapi, Gray(0xffuf8)) RGB(0x3fuf8, 0x3fuf8, 0x3fuf8) - mapi = BitShift{ARGB{UFixed8},2}() - @chk map(mapi, Gray(0xffuf8)) ARGB{UFixed8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0xffuf8) - @chk map(mapi, GrayA{UFixed8}(Gray(0xffuf8),0x3fuf8)) ARGB{UFixed8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0x0fuf8) - mapi = BitShift{RGBA{UFixed8},2}() - @chk map(mapi, Gray(0xffuf8)) RGBA{UFixed8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0xffuf8) - @chk map(mapi, GrayA{UFixed8}(Gray(0xffuf8),0x3fuf8)) RGBA{UFixed8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0x0fuf8) - mapi = BitShift(ARGB{UFixed8}, 8) - @chk map(mapi, RGB{UFixed16}(1.0,0.8,0.6)) ARGB{UFixed8}(1.0,0.8,0.6,1.0) - mapi = BitShift(RGBA{UFixed8}, 8) - @chk map(mapi, RGB{UFixed16}(1.0,0.8,0.6)) RGBA{UFixed8}(1.0,0.8,0.6,1.0) + mapi = BitShift{ARGB{N0f8},2}() + @chk map(mapi, Gray(0xffuf8)) ARGB{N0f8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0xffuf8) + @chk map(mapi, GrayA{N0f8}(Gray(0xffuf8),0x3fuf8)) ARGB{N0f8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0x0fuf8) + mapi = BitShift{RGBA{N0f8},2}() + @chk map(mapi, Gray(0xffuf8)) RGBA{N0f8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0xffuf8) + @chk map(mapi, GrayA{N0f8}(Gray(0xffuf8),0x3fuf8)) RGBA{N0f8}(0x3fuf8, 0x3fuf8, 0x3fuf8, 0x0fuf8) + mapi = BitShift(ARGB{N0f8}, 8) + @chk map(mapi, RGB{N0f16}(1.0,0.8,0.6)) ARGB{N0f8}(1.0,0.8,0.6,1.0) + mapi = BitShift(RGBA{N0f8}, 8) + @chk map(mapi, RGB{N0f16}(1.0,0.8,0.6)) RGBA{N0f8}(1.0,0.8,0.6,1.0) # Issue, #269, IJulia issue #294 - bs = BitShift(Gray{UFixed8}, 8) - v = Gray(UFixed16(0.8)) - @chk map(bs, v) Gray{UFixed8}(0.8) + bs = BitShift(Gray{N0f8}, 8) + v = Gray(ufixed16(0.8)) + @chk map(bs, v) Gray{N0f8}(0.8) end context("Clamp") do @@ -101,35 +101,35 @@ facts("Map") do mapi = Clamp(Float32) @chk map(mapi, 1.2) 1.0f0 @chk map(mapi, -1.2) 0.0f0 - mapi = Clamp(UFixed12) - @chk map(mapi, UFixed12(1.2)) one(UFixed12) - mapi = Clamp(Gray{UFixed12}) - @chk map(mapi, Gray(UFixed12(1.2))) Gray(one(UFixed12)) + mapi = Clamp(N4f12) + @chk map(mapi, N4f12(1.2)) one(N4f12) + mapi = Clamp(Gray{N4f12}) + @chk map(mapi, Gray(N4f12(1.2))) Gray(one(N4f12)) mapi = ClampMinMax(RGB24, 0.0, 1.0) - @chk map(mapi, 1.2) reinterpret(RGB24, 0x00ffffff) - @chk map(mapi, 0.5) reinterpret(RGB24, 0x00808080) - @chk map(mapi, -.3) reinterpret(RGB24, 0x00000000) - mapi = ClampMinMax(RGB{UFixed8}, 0.0, 1.0) - @chk map(mapi, 1.2) RGB{UFixed8}(1,1,1) - @chk map(mapi, 0.5) RGB{UFixed8}(0.5,0.5,0.5) - @chk map(mapi, -.3) RGB{UFixed8}(0,0,0) - mapi = Clamp(RGB{UFixed8}) - @chk map(mapi, RGB(1.2,0.5,-.3)) RGB{UFixed8}(1,0.5,0) - mapi = Clamp(ARGB{UFixed8}) - @chk map(mapi, ARGB{Float64}(1.2,0.5,-.3,0.2)) ARGB{UFixed8}(1.0,0.5,0.0,0.2) - @chk map(mapi, RGBA{Float64}(1.2,0.5,-.3,0.2)) ARGB{UFixed8}(1.0,0.5,0.0,0.2) - @chk map(mapi, 0.2) ARGB{UFixed8}(0.2,0.2,0.2,1.0) - @chk map(mapi, GrayA{Float32}(Gray(0.2),1.2)) ARGB{UFixed8}(0.2,0.2,0.2,1.0) - @chk map(mapi, GrayA{Float32}(Gray(-.4),0.8)) ARGB{UFixed8}(0.0,0.0,0.0,0.8) - mapi = Clamp(RGBA{UFixed8}) - @chk map(mapi, ARGB{Float64}(1.2,0.5,-.3,0.2)) RGBA{UFixed8}(1.0,0.5,0.0,0.2) - @chk map(mapi, RGBA{Float64}(1.2,0.5,-.3,0.2)) RGBA{UFixed8}(1.0,0.5,0.0,0.2) - @chk map(mapi, 0.2) RGBA{UFixed8}(0.2,0.2,0.2,1.0) - @chk map(mapi, GrayA{Float32}(Gray(0.2),1.2)) RGBA{UFixed8}(0.2,0.2,0.2,1.0) - @chk map(mapi, GrayA{Float32}(Gray(-.4),0.8)) RGBA{UFixed8}(0.0,0.0,0.0,0.8) + @chk map(mapi, 1.2) RGB24(0x00ffffff) + @chk map(mapi, 0.5) RGB24(0x00808080) + @chk map(mapi, -.3) RGB24(0x00000000) + mapi = ClampMinMax(RGB{N0f8}, 0.0, 1.0) + @chk map(mapi, 1.2) RGB{N0f8}(1,1,1) + @chk map(mapi, 0.5) RGB{N0f8}(0.5,0.5,0.5) + @chk map(mapi, -.3) RGB{N0f8}(0,0,0) + mapi = Clamp(RGB{N0f8}) + @chk map(mapi, RGB(1.2,0.5,-.3)) RGB{N0f8}(1,0.5,0) + mapi = Clamp(ARGB{N0f8}) + @chk map(mapi, ARGB{Float64}(1.2,0.5,-.3,0.2)) ARGB{N0f8}(1.0,0.5,0.0,0.2) + @chk map(mapi, RGBA{Float64}(1.2,0.5,-.3,0.2)) ARGB{N0f8}(1.0,0.5,0.0,0.2) + @chk map(mapi, 0.2) ARGB{N0f8}(0.2,0.2,0.2,1.0) + @chk map(mapi, GrayA{Float32}(Gray(0.2),1.2)) ARGB{N0f8}(0.2,0.2,0.2,1.0) + @chk map(mapi, GrayA{Float32}(Gray(-.4),0.8)) ARGB{N0f8}(0.0,0.0,0.0,0.8) + mapi = Clamp(RGBA{N0f8}) + @chk map(mapi, ARGB{Float64}(1.2,0.5,-.3,0.2)) RGBA{N0f8}(1.0,0.5,0.0,0.2) + @chk map(mapi, RGBA{Float64}(1.2,0.5,-.3,0.2)) RGBA{N0f8}(1.0,0.5,0.0,0.2) + @chk map(mapi, 0.2) RGBA{N0f8}(0.2,0.2,0.2,1.0) + @chk map(mapi, GrayA{Float32}(Gray(0.2),1.2)) RGBA{N0f8}(0.2,0.2,0.2,1.0) + @chk map(mapi, GrayA{Float32}(Gray(-.4),0.8)) RGBA{N0f8}(0.0,0.0,0.0,0.8) # Issue #253 - mapi = Clamp(BGRA{UFixed8}) - @chk map(mapi, RGBA{Float32}(1.2,0.5,-.3,0.2)) BGRA{UFixed8}(1.0,0.5,0.0,0.2) + mapi = Clamp(BGRA{N0f8}) + @chk map(mapi, RGBA{Float32}(1.2,0.5,-.3,0.2)) BGRA{N0f8}(1.0,0.5,0.0,0.2) @chk clamp(RGB{Float32}(-0.2,0.5,1.8)) RGB{Float32}(0.0,0.5,1.0) @chk clamp(ARGB{Float64}(1.2,0.5,-.3,0.2)) ARGB{Float64}(1.0,0.5,0.0,0.2) @@ -139,20 +139,20 @@ facts("Map") do context("Issue #285") do a = [Gray(0xd0uf8)] a1 = 10*a - mapi = mapinfo(Gray{UFixed8}, a1) + mapi = mapinfo(Gray{N0f8}, a1) @chk map(mapi, a1[1]) Gray(0xffuf8) end context("ScaleMinMax") do - mapi = ScaleMinMax(UFixed8, 100, 1000) - @chk map(mapi, 100) UFixed8(0.0) - @chk map(mapi, 1000) UFixed8(1.0) - @chk map(mapi, 10) UFixed8(0.0) - @chk map(mapi, 2000) UFixed8(1.0) - @chk map(mapi, 550) UFixed8(0.5) - mapinew = ScaleMinMax(UFixed8, [100,500,1000]) + mapi = ScaleMinMax(N0f8, 100, 1000) + @chk map(mapi, 100) N0f8(0.0) + @chk map(mapi, 1000) N0f8(1.0) + @chk map(mapi, 10) N0f8(0.0) + @chk map(mapi, 2000) N0f8(1.0) + @chk map(mapi, 550) N0f8(0.5) + mapinew = ScaleMinMax(N0f8, [100,500,1000]) @fact mapinew --> mapi - mapinew = ScaleMinMax(UFixed8, [0,500,2000], convert(UInt16, 100), convert(UInt16, 1000)) + mapinew = ScaleMinMax(N0f8, [0,500,2000], convert(UInt16, 100), convert(UInt16, 1000)) @fact mapinew --> mapi mapi = ScaleMinMax(ARGB32, 100, 1000) @chk map(mapi, 100) ARGB32(0,0,0,1) @@ -162,22 +162,22 @@ facts("Map") do @chk map(mapi, 50) RGB(0.0f0, 0.0f0, 0.0f0) @chk map(mapi, 550) RGB{Float32}(0.5, 0.5, 0.5) @chk map(mapi,2000) RGB(1.0f0, 1.0f0, 1.0f0) - A = Gray{UFixed8}[UFixed8(0.1), UFixed8(0.9)] + A = Gray{N0f8}[N0f8(0.1), N0f8(0.9)] @fact mapinfo(RGB24, A) --> MapNone{RGB24}() - mapi = ScaleMinMax(RGB24, A, zero(Gray{UFixed8}), one(Gray{UFixed8})) + mapi = ScaleMinMax(RGB24, A, zero(Gray{N0f8}), one(Gray{N0f8})) @fact map(mapi, A) --> map(mapinfo(RGB24, A), A) - mapi = ScaleMinMax(Float32, [Gray(one(UFixed8))], 0, 1) # issue #180 - @chk map(mapi, Gray(UFixed8(0.6))) 0.6f0 + mapi = ScaleMinMax(Float32, [Gray(one(N0f8))], 0, 1) # issue #180 + @chk map(mapi, Gray(N0f8(0.6))) 0.6f0 @fact_throws ErrorException ScaleMinMax(Float32, 0, 0, 1.0) # issue #245 A = [Gray{Float64}(0.2)] - mapi = ScaleMinMax(RGB{UFixed8}, A, 0.0, 0.2) - @fact map(mapi, A) --> [RGB{UFixed8}(1,1,1)] - mapi = ScaleMinMax(Gray{U8}, Gray{U8}(0.2), Gray{U8}(0.4)) - @fact Gray{U8}(0.49) <= map(mapi, Gray{U8}(0.3)) <= Gray{U8}(0.5) --> true - @fact Gray{U8}(0.49) <= map(mapi, 0.3) <= Gray{U8}(0.5) --> true - mapi = ScaleMinMax(Gray{U8}, 0.2, 0.4) - @fact Gray{U8}(0.49) <= map(mapi, Gray{U8}(0.3)) <= Gray{U8}(0.5) --> true - @fact Gray{U8}(0.49) <= map(mapi, 0.3) <= Gray{U8}(0.5) --> true + mapi = ScaleMinMax(RGB{N0f8}, A, 0.0, 0.2) + @fact map(mapi, A) --> [RGB{N0f8}(1,1,1)] + mapi = ScaleMinMax(Gray{N0f8}, Gray{N0f8}(0.2), Gray{N0f8}(0.4)) + @fact Gray{N0f8}(0.49) <= map(mapi, Gray{N0f8}(0.3)) <= Gray{N0f8}(0.5) --> true + @fact Gray{N0f8}(0.49) <= map(mapi, 0.3) <= Gray{N0f8}(0.5) --> true + mapi = ScaleMinMax(Gray{N0f8}, 0.2, 0.4) + @fact Gray{N0f8}(0.49) <= map(mapi, Gray{N0f8}(0.3)) <= Gray{N0f8}(0.5) --> true + @fact Gray{N0f8}(0.49) <= map(mapi, 0.3) <= Gray{N0f8}(0.5) --> true A = [-0.5 0.5; Inf NaN] @fact map(Clamp01NaN(A), A) --> [0.0 0.5; 1.0 0.0] @@ -189,8 +189,8 @@ facts("Map") do @fact map(smm, 0.0) --> exactly(0x00) @fact map(smm, 1.0) --> exactly(0xff) @fact map(smm, 0.1) --> exactly(round(UInt8, 0.1*255.0f0)) - smm = ScaleMinMax(Gray{U8}, typemin(Int8), typemax(Int8)) - @fact map(smm, 2) --> Gray{U8}(0.51) + smm = ScaleMinMax(Gray{N0f8}, typemin(Int8), typemax(Int8)) + @fact map(smm, 2) --> Gray{N0f8}(0.51) smm = ScaleMinMax(RGB24, typemin(Int8), typemax(Int8)) @fact map(smm, 2) --> reinterpret(RGB24, 0x828282) end @@ -218,23 +218,23 @@ facts("Map") do # Issue #304 A = rand(UInt16, 3, 2, 2) imgr = colorim(A) - mi1 = ScaleAutoMinMax(RGB{UFixed16}) + mi1 = ScaleAutoMinMax(RGB{N0f16}) res1 = raw(map(mi1, imgr)) - mi2 = ScaleAutoMinMax(UFixed16) + mi2 = ScaleAutoMinMax(N0f16) res2 = raw(map(mi2, raw(imgr))) # @fact res1 --> res2 # Note: this fails occassionally. Reproduce it with # s = 1.1269798f0 # val = 0xdeb5 - # UFixed16(s*UFixed16(val,0)) == UFixed16((s/typemax(UInt16))*val) - @fact maximum(abs, convert(Array{Int32}, res1) - convert(Array{Int32}, res2)) --> less_than_or_equal(1) + # N0f16(s*N0f16(val,0)) == N0f16((s/typemax(UInt16))*val) + @fact maxabs(convert(Array{Int32}, res1) - convert(Array{Int32}, res2)) --> less_than_or_equal(1) end context("Scaling and ssd") do img = Images.grayim(fill(typemax(UInt16), 3, 3)) - mapi = Images.mapinfo(UFixed8, img) + mapi = Images.mapinfo(N0f8, img) img8 = map(mapi, img) - @fact all(img8 .== typemax(UFixed8)) --> true + @fact all(img8 .== typemax(N0f8)) --> true A = 0 mnA, mxA = 1.0, -1.0 while mnA > 0 || mxA < 0 @@ -243,23 +243,23 @@ facts("Map") do end offset = 30.0 img = convert(Images.Image, A .+ offset) - mapi = Images.ScaleMinMax(UFixed8, offset, offset+mxA, 1/mxA) + mapi = Images.ScaleMinMax(N0f8, offset, offset+mxA, 1/mxA) imgs = map(mapi, img) @fact minimum(imgs) --> 0 @fact maximum(imgs) --> 1 - @fact eltype(imgs) --> UFixed8 + @fact eltype(imgs) --> N0f8 imgs = Images.imadjustintensity(img) @fact_throws MethodError Images.imadjustintensity(img, [1]) mnA = minimum(A) - @fact Images.ssd(imgs, (A.-mnA)/(mxA-mnA)) --> less_than(eps(UFixed16)) + @fact Images.ssd(imgs, (A.-mnA)/(mxA-mnA)) --> less_than(eps(N0f16)) A = reshape(1:9, 3, 3) B = map(Images.ClampMin(Float32, 3), A) @fact (eltype(B) == Float32 && B == [3 4 7; 3 5 8; 3 6 9]) --> true B = map(Images.ClampMax(UInt8, 7), A) @fact (eltype(B) == UInt8 && B == [1 4 7; 2 5 7; 3 6 7]) --> true - A = reinterpret(UFixed8, [convert(UInt8,1):convert(UInt8,24);], (3, 2, 4)) - img = reinterpret(RGB{UFixed8}, A, (2,4)) + A = reinterpret(N0f8, [convert(UInt8,1):convert(UInt8,24);], (3, 2, 4)) + img = reinterpret(RGB{N0f8}, A, (2,4)) @fact separate(img) --> permutedims(A, (2,3,1)) end @@ -300,10 +300,10 @@ facts("Map") do end context("Map and indexed images") do - img = Images.ImageCmap([1 2 3; 3 2 1], [RGB{UFixed16}(1.0,0.6,0.4), RGB{UFixed16}(0.2, 0.4, 0.6), RGB{UFixed16}(0.5,0.5,0.5)]) - mapi = MapNone(RGB{UFixed8}) + img = Images.ImageCmap([1 2 3; 3 2 1], [RGB{N0f16}(1.0,0.6,0.4), RGB{N0f16}(0.2, 0.4, 0.6), RGB{N0f16}(0.5,0.5,0.5)]) + mapi = MapNone(RGB{N0f8}) imgd = map(mapi, img) - cmap = [RGB{UFixed8}(1.0,0.6,0.4), RGB{UFixed8}(0.2, 0.4, 0.6), RGB{UFixed8}(0.5,0.5,0.5)] + cmap = [RGB{N0f8}(1.0,0.6,0.4), RGB{N0f8}(0.2, 0.4, 0.6), RGB{N0f8}(0.5,0.5,0.5)] @fact imgd --> reshape(cmap[[1,3,2,2,3,1]], (2,3)) end end diff --git a/test/old/overlays.jl b/test/old/overlays.jl index ee26433f..a097d121 100644 --- a/test/old/overlays.jl +++ b/test/old/overlays.jl @@ -6,11 +6,11 @@ facts("Overlay") do context("One") do ovr = Images.Overlay((2gray, 2gray), (RGB(1, 0, 0), RGB(0, 0, 1)), (Clamp{Float64}(), Clamp{Float64}())) @fact ovr[1] --> RGB(0, 0, 0) "test XcqmPT" - @fact ovr[2] --> RGB{U8}(0.5, 0, 0.5) "test yAFSVQ" + @fact ovr[2] --> RGB{N0f8}(0.5, 0, 0.5) "test yAFSVQ" @fact ovr[3] --> ovr[4] "test dyKCV9" @fact ovr[4] --> ovr[5] "test d4aUI1" @fact ovr[5] --> exactly(RGB(1, 0, 1)) "test UF1jSj" - @fact eltype(ovr) --> RGB{U8} "test Es9OnV" + @fact eltype(ovr) --> RGB{N0f8} "test Es9OnV" @fact length(ovr) --> 5 "test 04ptpL" @fact size(ovr) --> (5,) "test iE5wc4" @fact size(ovr, 1) --> 5 "test OvtQ4m" @@ -19,7 +19,7 @@ facts("Overlay") do @fact raw(ovr) --> [0x00 0x80 0xff 0xff 0xff; 0x00 0x00 0x00 0x00 0x00; 0x00 0x80 0xff 0xff 0xff] "test vLlExB" - @fact separate(ovr) --> UFixed8[0 0 0; + @fact separate(ovr) --> N0f8[0 0 0; 0.5 0 0.5; 1 0 1; 1 0 1; @@ -29,11 +29,11 @@ facts("Overlay") do end context("Two") do - ovr = Images.Overlay((gray, 0*gray), (RGB{UFixed8}(1, 0, 1), RGB{UFixed8}(0, 1, 0)), ((0, 1), (0, 1))) - @fact eltype(ovr) --> RGB{UFixed8} "test u7gavU" + ovr = Images.Overlay((gray, 0*gray), (RGB{N0f8}(1, 0, 1), RGB{N0f8}(0, 1, 0)), ((0, 1), (0, 1))) + @fact eltype(ovr) --> RGB{N0f8} "test u7gavU" ovr = collect(ovr) s = similar(ovr) - @fact typeof(s) --> Vector{RGB{UFixed8}} "test qOV0Iu" + @fact typeof(s) --> Vector{RGB{N0f8}} "test qOV0Iu" @fact length(s) --> 5 "test HxDDe4" s = similar(ovr, RGB{Float32}) @fact isa(s, Vector{RGB{Float32}}) --> true "test tPX9bh" diff --git a/test/old/writemime.jl b/test/old/writemime.jl index f33ebc14..f47b5b8a 100644 --- a/test/old/writemime.jl +++ b/test/old/writemime.jl @@ -2,49 +2,49 @@ using Images, FactCheck, Colors, FixedPointNumbers facts("show (MIME)") do # Test that we remembered to turn off Colors.jl's colorswatch display - @fact mimewritable(MIME("image/svg+xml"), rand(Gray{U8}, 5, 5)) --> false - @fact mimewritable(MIME("image/svg+xml"), rand(RGB{U8}, 5, 5)) --> false - @fact mimewritable(MIME("image/png"), rand(Gray{U8}, 5, 5)) --> true - @fact mimewritable(MIME("image/png"), rand(RGB{U8}, 5, 5)) --> true + @fact mimewritable(MIME("image/svg+xml"), rand(Gray{N0f8}, 5, 5)) --> false + @fact mimewritable(MIME("image/svg+xml"), rand(RGB{N0f8}, 5, 5)) --> false + @fact mimewritable(MIME("image/png"), rand(Gray{N0f8}, 5, 5)) --> true + @fact mimewritable(MIME("image/png"), rand(RGB{N0f8}, 5, 5)) --> true workdir = joinpath(tempdir(), "Images") if !isdir(workdir) mkdir(workdir) end context("no compression or expansion") do - A = U8[0.01 0.99; 0.25 0.75] + A = N0f8[0.01 0.99; 0.25 0.75] fn = joinpath(workdir, "writemime.png") open(fn, "w") do file show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=typemax(Int)) end - b = convert(Image{Gray{U8}}, load(fn)) + b = convert(Image{Gray{N0f8}}, load(fn)) @fact data(b) --> A end context("small images (expansion)") do - A = U8[0.01 0.99; 0.25 0.75] + A = N0f8[0.01 0.99; 0.25 0.75] fn = joinpath(workdir, "writemime.png") open(fn, "w") do file show(file, MIME("image/png"), grayim(A), minpixels=5, maxpixels=typemax(Int)) end - b = convert(Image{Gray{U8}}, load(fn)) + b = convert(Image{Gray{N0f8}}, load(fn)) @fact data(b) --> A[[1,1,2,2],[1,1,2,2]] end context("big images (use of restrict)") do - A = U8[0.01 0.4 0.99; 0.25 0.8 0.75; 0.6 0.2 0.0] + A = N0f8[0.01 0.4 0.99; 0.25 0.8 0.75; 0.6 0.2 0.0] Ar = restrict(A) fn = joinpath(workdir, "writemime.png") open(fn, "w") do file show(file, MIME("image/png"), grayim(A), minpixels=0, maxpixels=5) end - b = convert(Image{Gray{U8}}, load(fn)) - @fact data(b) --> convert(Array{U8}, Ar) + b = convert(Image{Gray{N0f8}}, load(fn)) + @fact data(b) --> convert(Array{N0f8}, Ar) # a genuinely big image (tests the defaults) abig = grayim(rand(UInt8, 1024, 1023)) fn = joinpath(workdir, "big.png") open(fn, "w") do file show(file, MIME("image/png"), abig, maxpixels=10^6) end - b = convert(Image{Gray{U8}}, load(fn)) - abigui = convert(Array{UFixed8,2}, data(restrict(abig, (1,2)))) + b = convert(Image{Gray{N0f8}}, load(fn)) + abigui = convert(Array{N0f8,2}, data(restrict(abig, (1,2)))) @fact data(b) --> abigui end end From fba35599f02e00f5e39f9c717a06d6517eaa9ecb Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 28 Jan 2017 10:42:08 -0600 Subject: [PATCH 30/38] Update Images docstring and make small NEWS tweaks --- NEWS.md | 19 ++++++++++++++++--- src/Images.jl | 17 ++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index 250580b3..04654a7a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -67,6 +67,10 @@ Key changes (of which many are breaking): particular note the `IIRGaussian` types which contain the functionality that was formerly in `imfilter_gaussian`. +- Nonlinear filtering operations have been added with + `mapwindow`. Among the supported functions is `median!`, thus + providing an implementation of median-filtering. + - Previous versions of Images used `reinterpret` for several operations, but `reinterpret` fails for most `AbstractArray`s other than `Array`. This release implements alternative mechanisms (e.g., @@ -85,9 +89,7 @@ Key changes (of which many are breaking): error message rather than just `InexactError` + Several bugs in FixedPointNumber operations have been fixed, and such operations are more consistent about return types - + A compact printing scheme is being tested in the - `teh/compact_printing` branch; check out the `fixed-renaming` - branch of many other packages to account for deprecations + + FixedPointNumbers are now printed more compactly - A new package, `ImageTransformations`, is underway for rotation, resizing, and other geometric operations on images. @@ -125,6 +127,17 @@ Other changes (all of which are breaking): than a `Vector{NTuple{N,Int}}`. This makes it ready for use in efficient indexing. +Changes in related packages: + +- NRRD.jl has been extensively revamped. The NRRD format lacks an + official test suite, and hence it was always uncertain how well the + package supported "the standard" (to the extent that there is + one). However, it was discovered that `unu make` can generate files + that can serve as a test suite, and using this strategy several + incompatibilities in our former version were noted and fixed. It is + possible that old .nrrd files written by julia might not be readable + without making manual edits to the header. + # Older versions For earlier history, please see the git revision history. diff --git a/src/Images.jl b/src/Images.jl index d8c64566..4a1d0c17 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -210,27 +210,22 @@ _length(A::AbstractArray) = length(linearindices(A)) _length(A) = length(A) """ -`Images` is a package for representing and processing images. - Constructors, conversions, and traits: - - Construction: `Image`, `ImageCmap`, `grayim`, `colorim`, `convert`, `copyproperties`, `shareproperties` - - Traits: `colordim`, `colorspace`, `coords_spatial`, `data`, `isdirect`, `isxfirst`, `isyfirst`, `pixelspacing`, `properties`, `sdims`, `spacedirections`, `spatialorder`, `storageorder`, `timedim` - - Size-related traits: `height`, `nchannels`, `ncolorelem`, `nimages`, `size_spatial`, `width`, `widthheight` - - Trait assertions: `assert_2d`, `assert_scalar_color`, `assert_timedim_last`, `assert_xfirst`, `assert_yfirst` - - Indexing operations: `getindexim`, `sliceim`, `subim` - - Conversions: `convert`, `raw`, `reinterpret`, `separate` + - Construction: use constructors of specialized packages, e.g., `AxisArray`, `ImageMeta`, etc. + - "Conversion": `colorview`, `channelview`, `rawview`, `ufixedview`, `permuteddimsview` + - Traits: `pixelspacing`, `sdims`, `timeaxis`, `timedim`, `spacedirections` Contrast/coloration: - - `MapInfo`: `MapNone`, `BitShift`, `ClampMinMax`, `ScaleMinMax`, `ScaleAutoMinMax`, `sc`, etc. + - `clamp01`, `clamp01nan`, `scaleminmax`, `colorsigned`, `scalesigned` Algorithms: - Reductions: `maxfinite`, `maxabsfinite`, `minfinite`, `meanfinite`, `sad`, `ssd`, `integral_image`, `boxdiff`, `gaussian_pyramid` - Resizing: `restrict`, `imresize` (not yet exported) - - Filtering: `imfilter`, `imfilter_fft`, `imfilter_gaussian`, `imfilter_LoG`, `imROF`, `ncc`, `padarray` - - Filtering kernels: `ando[345]`, `guassian2d`, `imaverage`, `imdog`, `imlaplacian`, `prewitt`, `sobel` + - Filtering: `imfilter`, `imfilter!`, `imfilter_LoG`, `mapwindow`, `imROF`, `padarray` + - Filtering kernels: `Kernel.` or `KernelFactors.`, followed by `ando[345]`, `guassian2d`, `imaverage`, `imdog`, `imlaplacian`, `prewitt`, `sobel` - Exposure : `imhist`, `histeq`, `adjust_gamma`, `histmatch`, `imadjustintensity`, `imstretch`, `imcomplement`, `clahe`, `cliphist` - Gradients: `backdiffx`, `backdiffy`, `forwarddiffx`, `forwarddiffy`, `imgradients` - Edge detection: `imedge`, `imgradients`, `thin_edges`, `magnitude`, `phase`, `magnitudephase`, `orientation`, `canny` From d3da965887e237118ddc824e90c2d685da7c5f18 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 28 Jan 2017 10:56:00 -0600 Subject: [PATCH 31/38] more FPN fixes --- src/Images.jl | 2 +- src/exposure.jl | 2 +- src/map-deprecated.jl | 28 ++++++++++++++-------------- test/old/core.jl | 6 +++--- test/old/map.jl | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Images.jl b/src/Images.jl index 4a1d0c17..99dac147 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -213,7 +213,7 @@ _length(A) = length(A) Constructors, conversions, and traits: - Construction: use constructors of specialized packages, e.g., `AxisArray`, `ImageMeta`, etc. - - "Conversion": `colorview`, `channelview`, `rawview`, `ufixedview`, `permuteddimsview` + - "Conversion": `colorview`, `channelview`, `rawview`, `normedview`, `permuteddimsview` - Traits: `pixelspacing`, `sdims`, `timeaxis`, `timedim`, `spacedirections` Contrast/coloration: diff --git a/src/exposure.jl b/src/exposure.jl index 0d4d1a0f..88b913a1 100644 --- a/src/exposure.jl +++ b/src/exposure.jl @@ -173,7 +173,7 @@ This is the combined with the I and Q channels and the resulting image converted type as the input. """ -function adjust_gamma{T<:FixedPointNumbers.UFixed}(img::AbstractArray{Gray{T}}, gamma::Number) +function adjust_gamma{T<:FixedPointNumbers.Normed}(img::AbstractArray{Gray{T}}, gamma::Number) raw_type = FixedPointNumbers.rawtype(T) gamma_inv = 1.0 / gamma table = zeros(T, typemax(raw_type) + 1) diff --git a/src/map-deprecated.jl b/src/map-deprecated.jl index 39fc5e0e..70db8eb8 100644 --- a/src/map-deprecated.jl +++ b/src/map-deprecated.jl @@ -1,7 +1,7 @@ #### Elementwise manipulations (scaling/clamping/type conversion) #### # This file exists primarily to handle conversions for display and -# saving to disk. Both of these operations require UFixed-valued +# saving to disk. Both of these operations require Normed-valued # elements, but with display we always want to convert to 8-bit # whereas saving can handle 16-bit. # We also can't trust that user images are clamped properly. @@ -122,13 +122,13 @@ similar{S,T,N}(mapi::BitShift{S,N}, ::Type{T}, ::Type) = BitShift{T,N}() # Implementation immutable BS{N} end _immap{T<:Unsigned,N}(::Type{T}, ::Type{BS{N}}, val::Unsigned) = (v = val>>>N; tm = oftype(val, typemax(T)); convert(T, ifelse(v > tm, tm, v))) -_immap{T<:UFixed,N}(::Type{T}, ::Type{BS{N}}, val::UFixed) = reinterpret(T, _immap(FixedPointNumbers.rawtype(T), BS{N}, reinterpret(val))) +_immap{T<:Normed,N}(::Type{T}, ::Type{BS{N}}, val::Normed) = reinterpret(T, _immap(FixedPointNumbers.rawtype(T), BS{N}, reinterpret(val))) immap{T<:Real,N}(mapi::BitShift{T,N}, val::Real) = _immap(T, BS{N}, val) immap{T<:Real,N}(mapi::BitShift{T,N}, val::Gray) = _immap(T, BS{N}, val.val) immap{T<:Real,N}(mapi::BitShift{Gray{T},N}, val::Gray) = Gray(_immap(T, BS{N}, val.val)) map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::Unsigned) = _immap(UInt8, BS{N}, val) -map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::UFixed) = _immap(N0f8, BS{N}, val) -map1{CT<:Colorant,N}(mapi::BitShift{CT,N}, val::UFixed) = _immap(eltype(CT), BS{N}, val) +map1{N}(mapi::Union{BitShift{RGB24,N},BitShift{ARGB32,N}}, val::Normed) = _immap(N0f8, BS{N}, val) +map1{CT<:Colorant,N}(mapi::BitShift{CT,N}, val::Normed) = _immap(eltype(CT), BS{N}, val) ## Clamp types @@ -572,7 +572,7 @@ end #### MapInfo defaults -# Each "client" can define its own methods. "clients" include UFixed, +# Each "client" can define its own methods. "clients" include Normed, # RGB24/ARGB32, and ImageMagick const bitshiftto8 = ((N6f10, 2), (N4f12, 4), (N2f14, 6), (N0f16, 8)) @@ -594,7 +594,7 @@ You can define your own rules for `mapinfo`. For example, the `ImageMagick` package defines methods for how pixels values should be converted before saving images to disk. """ -mapinfo{T<:UFixed}(::Type{T}, img::AbstractArray{T}) = MapNone(img) +mapinfo{T<:Normed}(::Type{T}, img::AbstractArray{T}) = MapNone(img) mapinfo{T<:AbstractFloat}(::Type{T}, img::AbstractArray{T}) = MapNone(img) # Grayscale methods @@ -607,8 +607,8 @@ for (T,n) in bitshiftto8 @eval mapinfo(::Type{Gray{N0f8}}, img::GrayArray{$T}) = BitShift{Gray{N0f8},$n}() @eval mapinfo(::Type{GrayA{N0f8}}, img::AbstractArray{GrayA{$T}}) = BitShift{GrayA{N0f8},$n}() end -mapinfo{T<:UFixed,F<:AbstractFloat}(::Type{T}, img::GrayArray{F}) = ClampMinMax(T, zero(F), one(F)) -mapinfo{T<:UFixed,F<:AbstractFloat}(::Type{Gray{T}}, img::GrayArray{F}) = ClampMinMax(Gray{T}, zero(F), one(F)) +mapinfo{T<:Normed,F<:AbstractFloat}(::Type{T}, img::GrayArray{F}) = ClampMinMax(T, zero(F), one(F)) +mapinfo{T<:Normed,F<:AbstractFloat}(::Type{Gray{T}}, img::GrayArray{F}) = ClampMinMax(Gray{T}, zero(F), one(F)) mapinfo{T<:AbstractFloat, R<:Real}(::Type{T}, img::AbstractArray{R}) = MapNone(T) mapinfo(::Type{RGB24}, img::Union{AbstractArray{Bool}, BitArray}) = MapNone{RGB24}() @@ -666,16 +666,16 @@ mapinfo(::Type{UInt32}, img::Union{AbstractArray{Bool},BitArray}) = mapinfo(RGB2 mapinfo(::Type{UInt32}, img::AbstractArray{UInt32}) = MapNone{UInt32}() -# Clamping mapinfo client. Converts to RGB and uses UFixed, clamping +# Clamping mapinfo client. Converts to RGB and uses Normed, clamping # floating-point values to [0,1]. -mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{T}) = MapNone{T}() +mapinfo{T<:Normed}(::Type{Clamp}, img::AbstractArray{T}) = MapNone{T}() mapinfo{T<:AbstractFloat}(::Type{Clamp}, img::AbstractArray{T}) = ClampMinMax(N0f8, zero(T), one(T)) let handled = Set() for ACV in (Color, AbstractRGB) for CV in subtypes(ACV) (length(CV.parameters) == 1 && !(CV.abstract)) || continue CVnew = CV<:AbstractGray ? Gray : RGB - @eval mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$CV{T}}) = MapNone{$CVnew{T}}() + @eval mapinfo{T<:Normed}(::Type{Clamp}, img::AbstractArray{$CV{T}}) = MapNone{$CVnew{T}}() @eval mapinfo{CV<:$CV}(::Type{Clamp}, img::AbstractArray{CV}) = Clamp{$CVnew{N0f8}}() CVnew = CV<:AbstractGray ? Gray : BGR AC, CA = alphacolor(CV), coloralpha(CV) @@ -685,9 +685,9 @@ for ACV in (Color, AbstractRGB) push!(handled, AC) ACnew, CAnew = alphacolor(CVnew), coloralpha(CVnew) @eval begin - mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$AC{T}}) = MapNone{$ACnew{T}}() + mapinfo{T<:Normed}(::Type{Clamp}, img::AbstractArray{$AC{T}}) = MapNone{$ACnew{T}}() mapinfo{P<:$AC}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$ACnew{N0f8}}() - mapinfo{T<:UFixed}(::Type{Clamp}, img::AbstractArray{$CA{T}}) = MapNone{$CAnew{T}}() + mapinfo{T<:Normed}(::Type{Clamp}, img::AbstractArray{$CA{T}}) = MapNone{$CAnew{T}}() mapinfo{P<:$CA}(::Type{Clamp}, img::AbstractArray{P}) = Clamp{$CAnew{N0f8}}() end end @@ -708,5 +708,5 @@ Applies default or specified `ScaleMinMax` mapping to the image. sc(img::AbstractArray) = immap(ScaleMinMax(N0f8, img), img) sc(img::AbstractArray, mn::Real, mx::Real) = immap(ScaleMinMax(N0f8, img, mn, mx), img) -ufixedsc{T<:UFixed}(::Type{T}, img::AbstractArray) = immap(mapinfo(T, img), img) +ufixedsc{T<:Normed}(::Type{T}, img::AbstractArray) = immap(mapinfo(T, img), img) ufixed8sc(img::AbstractArray) = ufixedsc(N0f8, img) diff --git a/test/old/core.jl b/test/old/core.jl index 10bc115e..bd7da76c 100644 --- a/test/old/core.jl +++ b/test/old/core.jl @@ -12,7 +12,7 @@ const scalar_getindex_new = VERSION >= v"0.5.0-dev+1195" facts("Core") do a = rand(3,3) @inferred(Image(a)) - # support integer-valued types, but these are NOT recommended (use UFixed) + # support integer-valued types, but these are NOT recommended (use Normed) B = rand(UInt16(1):UInt16(20), 3, 5) # img, imgd, and imgds will be used in many more tests # Thus, these must be defined as local if reassigned in any context() blocks @@ -32,7 +32,7 @@ facts("Core") do context("Constructors of Image types") do @fact colorspace(B) --> "Gray" "test h42DSP" @fact colordim(B) --> 0 "test 4ZQwBT" - let img = colorview(RGB, ufixedview(UFixed{UInt16,8}, B)) + let img = colorview(RGB, normedview(Normed{UInt16,8}, B)) @fact colorspace(img) --> "RGB" "test THoeTd" @fact colordim(img) --> 0 "test Zm2gDZ" img = grayim(B) @@ -343,7 +343,7 @@ facts("Core") do @fact convert(Image{RGB{N0f8}}, imrgb8) --> exactly(imrgb8) "test 39AZU3" im8 = reinterpret(N0f8, imrgb8) @fact data(im8) --> A8 "test lZSAH9" - @fact permutedims(ufixedview(N0f8, separate(imrgb8)), (3, 1, 2)) --> im8 "test zDOWZM" + @fact permutedims(normedview(N0f8, separate(imrgb8)), (3, 1, 2)) --> im8 "test zDOWZM" @fact reinterpret(UInt8, imrgb8) --> Au8 "test HeezpR" @fact reinterpret(RGB, im8) --> imrgb8 "test VJUpj3" ims8 = separate(imrgb8) diff --git a/test/old/map.jl b/test/old/map.jl index 721154a5..0e37226f 100644 --- a/test/old/map.jl +++ b/test/old/map.jl @@ -211,7 +211,7 @@ facts("Map") do @compat context("ScaleAutoMinMax") do mapi = ScaleAutoMinMax() A = [100,550,1000] - @chk map(mapi, A) UFixed8.([0.0,0.5,1.0]) + @chk map(mapi, A) N0f8.([0.0,0.5,1.0]) mapi = ScaleAutoMinMax(RGB24) @chk map(mapi, A) reinterpret(RGB24, [0x00000000, 0x00808080, 0x00ffffff]) From a0d155ab85756aaba08467e46fa6b4300131fe99 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 28 Jan 2017 10:57:47 -0600 Subject: [PATCH 32/38] Update README to link to JuliaImages docs --- .travis.yml | 13 +-- README.md | 288 +--------------------------------------------------- 2 files changed, 6 insertions(+), 295 deletions(-) diff --git a/.travis.yml b/.travis.yml index 43acccdb..a5fa9a75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,14 +7,11 @@ julia: - nightly notifications: email: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("Images")' - - julia -e 'Pkg.add("ImageMagick"); Pkg.checkout("ImageMagick", "images-next")' - - julia -e 'Pkg.test("Images", coverage=true)' +# script: +# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi +# - julia -e 'Pkg.clone(pwd()); Pkg.build("Images")' +# - julia -e 'Pkg.test("Images", coverage=true)' after_success: - - if [ $TRAVIS_JULIA_VERSION = "0.4" ] && [ $TRAVIS_OS_NAME = "linux" ]; then + - if [ $TRAVIS_JULIA_VERSION = "0.5" ] && [ $TRAVIS_OS_NAME = "linux" ]; then julia -e 'cd(Pkg.dir("Images")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())'; fi - - julia -e 'Pkg.clone("https://github.com/MichaelHatherly/Documenter.jl")' - - julia -e 'cd(Pkg.dir("Images")); include(joinpath("docs", "make.jl"))' diff --git a/README.md b/README.md index 00436e6e..b6468b7c 100644 --- a/README.md +++ b/README.md @@ -11,293 +11,7 @@ An image processing library for [Julia](http://julialang.org/). ## Documentation -Full documentation is found here: - -[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://timholy.github.io/Images.jl/stable) -[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://timholy.github.io/Images.jl/latest) - -## Installation - -Install via the package manager: - -``` -Pkg.add("Images") -``` - -## Image I/O - -It's helpful to have one or more image I/O libraries (see -https://github.com/JuliaIO) installed on your system, as Images relies -on them for reading and writing many common image types. These -libraries are managed by -[FileIO](https://github.com/JuliaIO/FileIO.jl), and in interactive -usage it should prompt you to install any dependencies. - -If you have problems loading or saving Images, please report bugs to -the appropriate I/O packages. This package does not perform any I/O on -its own. - -## Package interactions - -A few other packages define overlapping functions or types (e.g., [Winston](https://github.com/nolta/Winston.jl) defines `Image`). -When using both Images and these packages, you can always specify which version you want with `Images.Image(data, properties)`. - -## Image viewing - -If you're using the [IJulia](https://github.com/JuliaLang/IJulia.jl) notebook, images will be displayed [automatically](http://htmlpreview.github.com/?https://github.com/timholy/Images.jl/blob/master/ImagesDemo.html). - -Julia code for the display of images can be found in [ImageView](https://github.com/timholy/ImageView.jl). -Installation of this package is recommended but not required. - -## TestImages - -When testing ideas or just following along with the documentation, it can be useful to have some images to work with. -The [TestImages](https://github.com/timholy/TestImages.jl) package bundles several "standard" images for you. -To load one of the images from this package, say -``` -using TestImages -img = testimage("mandrill") -``` -The examples below will assume you're loading a particular file from your disk, but you can substitute those -commands with `testimage`. - -## Getting started - -For these examples you'll need to install both `Images` and `ImageView`. -Depending on your task, it's also very useful to have two other packages -loaded, [Colors](https://github.com/JuliaGraphics/Colors.jl) and -[FixedPointNumbers](https://github.com/JeffBezanson/FixedPointNumbers.jl). -Load the code for all of these packages with - -```julia -using Images, Colors, FixedPointNumbers, ImageView -``` - -### Loading your first image: how images are represented - -You likely have a number of images already at your disposal, and you can use these, TestImages.jl, or -run `readremote.jl` in the `test/` directory. -(This requires an internet connection.) -These will be deposited inside an `Images` directory inside your temporary directory -(e.g., `/tmp` on Linux systems). The `"rose.png"` image in this example comes from the latter. - -Let's begin by reading an image from a file: -``` -julia> img = load("rose.png") -RGB Image with: - data: 70x46 Array{RGB{UFixed{Uint8,8}},2} - properties: - IMcs: sRGB - spatialorder: x y - pixelspacing: 1 1 -``` -If you're using Images through IJulia, rather than this text output you probably see the image itself. -This is nice, but often it's quite helpful to see the structure of these Image objects. -This happens automatically at the REPL; within IJulia you can call -``` -show(img) -``` -to see the output above. - -The first line tells you that this is an RGB image. -It is stored as a two-dimensional `Array` of `RGB{UFixed{Uint8,8}}`. -To see what this pixel type is, we can do the following: -``` -julia> img[1,1] -RGB{UFixed8}(0.188,0.184,0.176) -``` -This extracts the first pixel, the one visually at the upper-left of the image. You can see that -an `RGB` (which comes from the [Colors](https://github.com/JuliaGraphics/Colors.jl) package) is a triple of values. -The `UFixed8` number type (which comes from the -[FixedPointNumbers](https://github.com/JeffBezanson/FixedPointNumbers.jl) package), and whose long -name is `UFixed{Uint8,8}`) -represents fractional numbers, those that can encode values that lie between 0 and 1, using just 1 byte (8 bits). -If you've previously used other image processing libraries, you may be used to thinking of two basic -image types, floating point-valued and integer-valued. In those libraries, "saturated" -(the color white for an RGB image) would be -represented by `1.0` for floating point-valued images, 255 for a `Uint8` image, -and `0x0fff` for an image collected by a 12-bit camera. -`Images.jl`, via Colors and FixedPointNumbers, unifies these so that `1` always means saturated, no -matter whether the element type is `Float64`, `UFixed8`, or `UFixed12`. -This makes it easier to write generic algorithms and visualization code, -while still allowing one to use efficient (and C-compatible) raw representations. - -You can see that this image has `properties`, of which there are three: -`"IMcs"`, `"spatialorder"` and `"pixelspacing"`. -We'll talk more about the latter two in the next section. -The `"IMcs"` is really for internal use by ImageMagick; it says that the colorspace -is `"sRGB"`, although (depending on which version of the library you have) -you may see it say `"RGB"`. -Such differences are due to [changes](http://www.imagemagick.org/script/color-management.php) -in how ImageMagick handles colorspaces, and the fact that both older -and newer versions of the library are still widespread. - -You can retrieve the properties using `props = properties(img)`. -This returns the dictionary used by `img`; any modifications you make -to `props` will update the properties of `img`. - -Likewise, given an Image `img`, you can access the underlying array with -```julia -A = data(img) -``` -This is handy for those times when you want to call an algorithm that is implemented only -for `Array`s. At the end, however, you may want to restore the contextual information -available in an Image. While you can use the `Image` constructor directly, -two alternatives can be convenient: -```julia -imgc = copyproperties(img, A) -imgs = shareproperties(img, A) -``` -`imgc` has its own properties dictionary, initialized to be a copy of the one used by `img`. -In contrast, `imgs` shares a properties dictionary with `img`; any modification to the -properties of `img` will also modify them for `imgs`. Use either as appropriate to your -circumstance. - -The Images package is designed to work with either plain arrays or with Image types---in general, though, -you're probably best off leaving things as an Image, particularly if you work -with movies, 3d images, or other more complex objects. - -### Storage order and changing the representation of images - -In the example above, the `"spatialorder"` property has value `["x", "y"]`. -This indicates that the image data are in "horizontal-major" order, -meaning that a pixel at spatial location `(x,y)` would be addressed as `img[x,y]` -rather than `img[y,x]`. `["y", "x"]` would indicate vertical-major. -Consequently, this image is 70 pixels wide and 46 pixels high. - -Images returns this image in horizontal-major order because this is how it was stored on disk. -Because the Images package is designed to scale to terabyte-sized images, a general philosophy -is to work with whatever format users provide without forcing changes to the raw array representation. -Consequently, when you load an image, its representation will match that used in the file. - -Of course, if you prefer to work with plain arrays, you can convert it: -``` -julia> imA = convert(Array, img); - -julia> summary(imA) -"46x70 Array{RGB{UFixed{Uint8,8}},2}" -``` -You can see that this permuted the dimensions into vertical-major order, consistent -with the column-major order with which Julia stores `Arrays`. Note that this -preserved the element type, returning an `Array{RGB}`. -If you prefer to extract into an array of plain numbers in color-last order -(typical of Matlab), you can use -``` -julia> imsep = separate(img) -RGB Image with: - data: 46x70x3 Array{UFixed{Uint8,8},3} - properties: - IMcs: sRGB - colorspace: RGB - colordim: 3 - spatialorder: y x - pixelspacing: 1 1 -``` -You can see that `"spatialorder"` was changed to reflect the new layout, and that -two new properties were added: `"colordim"`, which specifies which dimension of the array -is used to encode color, and `"colorspace"` so you know how to interpret these colors. - -Compare this to -``` -julia> imr = reinterpret(UFixed8, img) -RGB Image with: - data: 3x70x46 Array{UFixed{Uint8,8},3} - properties: - IMcs: sRGB - colorspace: RGB - colordim: 1 - spatialorder: x y - pixelspacing: 1 1 -``` -`reinterpret` gives you a new view of the same underlying memory as `img`, whereas -`convert(Array, img)` and `separate(img)` create new arrays if the memory-layout -needs alteration. - -You can go back to using Colors to encode your image this way: -``` -julia> imcomb = convert(Image{RGB}, imsep) -RGB Image with: - data: 46x70 Array{RGB{UFixed{Uint8,8}},2} - properties: - IMcs: sRGB - spatialorder: y x - pixelspacing: 1 1 -``` -or even change to a new colorspace like this: -``` -julia> imhsv = convert(Image{HSV}, float32(img)) -HSV Image with: - data: 70x46 Array{HSV{Float32},2} - properties: - IMcs: sRGB - spatialorder: x y - pixelspacing: 1 1 -``` -Many of the colorspaces supported by Colors (or rather, its base package ColorTypes) need a wider range of values than `[0,1]`, -so it's necessary to convert to floating point. - -If you say `view(imhsv)`, you may be surprised to see something that looks -like the original RGB image. Since the colorspace is known, it converts -to RGB before rendering it. If, for example, you wanted to see what a -"pure-V" image looks like, you can do this: -```julia -imv = shareproperties(imhsv, [HSV(0, 0, imhsv[i,j].v) for i = 1:size(imhsv,1),j = 1:size(imhsv,2)]) -view(imv) -``` -and a pure-H image like this: -```julia -imh = shareproperties(imhsv, [HSV(imhsv[i,j].h, 0.5, 0.5) for i = 1:size(imhsv,1),j = 1:size(imhsv,2)]) -view(imh) -``` -(Hue without saturation or value generates gray or black, so we used a constant different from zero for these parameters.) - -![raw](docs/src/img/rose_hsv.png) - -Of course, you can combine these commands, for example -```julia -A = reinterpret(Uint8, data(img)) -``` -will, for a `RGB{UFixed8}` image, return a raw 3d array. -This can be useful if you want to interact with external code (a C-library, for example). -Assuming you don't want to lose orientation information, you can wrap a returned array `B` -as `shareproperties(img, B)`. - -### Other properties, and usage of Units - -The `"pixelspacing"` property informs ImageView that this image has an aspect ratio 1. -In scientific or medical imaging, you can use actual units to encode this property, -for example through the [SIUnits](https://github.com/Keno/SIUnits.jl) package. -For example, if you're doing microscopy you might specify -``` -using SIUnits -img["pixelspacing"] = [0.32Micro*Meter,0.32Micro*Meter] -``` -If you're performing three-dimensional imaging, you might set different values for the -different axes: -``` -using SIUnits.ShortUnits -mriscan["pixelspacing"] = [0.2mm, 0.2mm, 2mm] -``` - -ImageView includes facilities for scale bars, and by supplying your pixel spacing -you can ensure that the scale bars are accurate. - -### A brief demonstration of image processing - -Now let's work through a more sophisticated example: -``` -using Images, TestImages, ImageView -img = testimage("mandrill") -view(img) -# Let's do some blurring -kern = ones(Float32,7,7)/49 -imgf = imfilter(img, kern) -view(imgf) -# Let's make an oversaturated image -imgs = 2imgf -view(imgs) -``` -![processing](docs/src/img/mandrill.jpg) +Full documentation is found at [JuliaImages](http://juliaimages.github.io/latest/). # Credits From 664cb157f48110e4622591c6389b1139a3c19dc5 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 28 Jan 2017 12:20:33 -0600 Subject: [PATCH 33/38] Fix #187 --- src/algorithms.jl | 20 ++++++++++++++++++++ test/algorithms.jl | 12 ++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/algorithms.jl b/src/algorithms.jl index 3c1df177..e068727d 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -73,6 +73,26 @@ else _newindexer(shape, inds) = Base.Broadcast.shapeindexer(shape, inds) end +function Base.var{C<:AbstractGray}(A::AbstractArray{C}; kwargs...) + imgc = channelview(A) + base_colorant_type(C)(var(imgc; kwargs...)) +end + +function Base.var{C<:Colorant,N}(A::AbstractArray{C,N}; kwargs...) + imgc = channelview(A) + colons = ntuple(d->Colon(), Val{N}) + inds1 = indices(imgc, 1) + val1 = var(view(imgc, first(inds1), colons...); kwargs...) + vals = similar(imgc, typeof(val1), inds1) + vals[1] = val1 + for i in first(inds1)+1:last(inds1) + vals[i] = var(view(imgc, i, colons...); kwargs...) + end + base_colorant_type(C)(vals...) +end + +Base.std{C<:Colorant}(A::AbstractArray{C}; kwargs...) = mapc(sqrt, var(A; kwargs...)) + # Entropy for grayscale (intensity) images function _log(kind::Symbol) if kind == :shannon diff --git a/test/algorithms.jl b/test/algorithms.jl index f2aa7a3d..5e32ef6c 100644 --- a/test/algorithms.jl +++ b/test/algorithms.jl @@ -2,6 +2,18 @@ using Images using Base.Test @testset "Algorithms" begin + @testset "Statistics" begin + # issue #187 + for T in (N0f8, Float32) + A = rand(RGB{T}, 5, 4) + Ac = channelview(A) + s = std(A) + @test red(s) ≈ std(Ac[1,:,:]) + @test green(s) ≈ std(Ac[2,:,:]) + @test blue(s) ≈ std(Ac[3,:,:]) + end + end + @testset "Features" begin A = zeros(Int, 9, 9); A[5, 5] = 1 blobs = blob_LoG(A, 2.0.^[0.5,0,1]) From 652f2da4c5e102235c4cc20d3011afd75712104f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 29 Jan 2017 05:32:25 -0600 Subject: [PATCH 34/38] Turn off 0.4 testing on AppVeyor --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 3dc43259..b072fc62 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,5 @@ environment: matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" - JULIAVERSION: "julialang/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" - JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe" From 474ee4857c214265ee02d69c26dd057830bc2c88 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 29 Jan 2017 05:33:28 -0600 Subject: [PATCH 35/38] For now, run test suite with ImageMagick on OSX --- test/REQUIRE | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/REQUIRE b/test/REQUIRE index 5147a11a..0eb70bd0 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -2,5 +2,6 @@ FactCheck @windows ImageMagick @linux ImageMagick -@osx QuartzImageIO -OffsetArrays \ No newline at end of file +# @osx QuartzImageIO +@osx ImageMagick +OffsetArrays From ceb8d6ff91687dec670ce037f07b399a7e61c892 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 30 Jan 2017 05:46:54 -0600 Subject: [PATCH 36/38] Improve type-stability of entropy --- src/algorithms.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index e068727d..49206a88 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -96,33 +96,33 @@ Base.std{C<:Colorant}(A::AbstractArray{C}; kwargs...) = mapc(sqrt, var(A; kwargs # Entropy for grayscale (intensity) images function _log(kind::Symbol) if kind == :shannon - x -> log2.(x) + return log2 elseif kind == :nat - x -> log.(x) + return log elseif kind == :hartley - x -> log10.(x) + return log10 else throw(ArgumentError("Invalid entropy unit. (:shannon, :nat or :hartley)")) end end """ - entropy(img, kind) + entropy(logᵦ, img) + entropy(img; [kind=:shannon]) -Compute the entropy of a grayscale image defined as `-sum(p.*logb(p))`. -The base b of the logarithm (a.k.a. entropy unit) is one of the following: +Compute the entropy of a grayscale image defined as `-sum(p.*logᵦ(p))`. +The base β of the logarithm (a.k.a. entropy unit) is one of the following: -- `:shannon ` (log base 2, default) -- `:nat` (log base e) -- `:hartley` (log base 10) +- `:shannon ` (log base 2, default), or use logᵦ = log2 +- `:nat` (log base e), or use logᵦ = log +- `:hartley` (log base 10), or use logᵦ = log10 """ -function entropy(img::AbstractArray; kind=:shannon) - logᵦ = _log(kind) - +entropy(img::AbstractArray; kind=:shannon) = entropy(_log(kind), img) +function entropy{Log<:Function}(logᵦ::Log, img) hist = StatsBase.fit(Histogram, vec(img), nbins=256) counts = hist.weights p = counts / length(img) - logp = logᵦ(p) + logp = logᵦ.(p) # take care of empty bins logp[Bool[isinf(v) for v in logp]] = 0 From 1060fb7b1851c4923c38ed62e7673e0b68f5ecfc Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 30 Jan 2017 05:52:16 -0600 Subject: [PATCH 37/38] Add cross references to docstrings --- src/algorithms.jl | 85 ++--------------------------------------------- 1 file changed, 3 insertions(+), 82 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 49206a88..f23603cf 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -388,6 +388,8 @@ It has fields: - amplitude: the value of the `-LoG(σ)`-filtered image at the peak Note that the radius is equal to σ√2. + +See also: [`blob_LoG`](@ref). """ immutable BlobLoG{T,S,N} location::CartesianIndex{N} @@ -414,7 +416,7 @@ of `img`. Lindeberg T (1998), "Feature Detection with Automatic Scale Selection", International Journal of Computer Vision, 30(2), 79–116. -See also: BlobLoG. +See also: [`BlobLoG`](@ref). """ function blob_LoG{T,N}(img::AbstractArray{T,N}, σs, edges::Tuple{Vararg{Bool}}=(true, ntuple(d->false, Val{N})...)) sigmas = sort(σs) @@ -484,87 +486,6 @@ Like `findlocalmaxima`, but returns the coordinates of the smallest elements. findlocalminima(img::AbstractArray, region=coords_spatial(img), edges=true) = findlocalextrema(img, region, edges, Base.Order.Reverse) -# FIXME -# Laplacian of Gaussian filter -# Separable implementation from Huertas and Medioni, -# IEEE Trans. Pat. Anal. Mach. Int., PAMI-8, 651, (1986) -# """ -# ``` -# imgf = imfilter_LoG(img, sigma, [border]) -# ``` - -# filters a 2D image with a Laplacian of Gaussian of the specified width. `sigma` -# may be a vector with one value per array dimension, or may be a single scalar -# value for uniform filtering in both dimensions. Uses the Huertas and Medioni -# separable algorithm. -# """ -# function imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Vector, border="replicate") -# # Limited to 2D for now. -# # See Sage D, Neumann F, Hediger F, Gasser S, Unser M. -# # Image Processing, IEEE Transactions on. (2005) 14(9):1372-1383. -# # for 3D. - -# # Set up 1D kernels -# @assert length(σ) == 2 -# σx, σy = σ[1], σ[2] -# h1(ξ, σ) = sqrt(1/(2π*σ^4))*(1 - ξ^2/σ^2)exp(-ξ^2/(2σ^2)) -# h2(ξ, σ) = sqrt(1/(2π*σ^4))*exp(-ξ^2/(2σ^2)) - -# w = 8.5σx -# kh1x = Float64[h1(i, σx) for i = -floor(w/2):floor(w/2)] -# kh2x = Float64[h2(i, σx) for i = -floor(w/2):floor(w/2)] - -# w = 8.5σy -# kh1y = Float64[h1(i, σy) for i = -floor(w/2):floor(w/2)] -# kh2y = Float64[h2(i, σy) for i = -floor(w/2):floor(w/2)] - -# # Set up padding index lists -# kernlenx = length(kh1x) -# prepad = div(kernlenx - 1, 2) -# postpad = div(kernlenx, 2) -# Ix = padindexes(img, 1, prepad, postpad, border) - -# kernleny = length(kh1y) -# prepad = div(kernleny - 1, 2) -# postpad = div(kernleny, 2) -# Iy = padindexes(img, 2, prepad, postpad, border) - -# sz = size(img) -# # Store intermediate result in a transposed array -# # Allows column-major second stage filtering -# img1 = Array(Float64, (sz[2], sz[1])) -# img2 = Array(Float64, (sz[2], sz[1])) -# for j in 1:sz[2] -# for i in 1:sz[1] -# tmp1, tmp2 = 0.0, 0.0 -# for k in 1:kernlenx -# @inbounds tmp1 += kh1x[k] * img[Ix[i + k - 1], j] -# @inbounds tmp2 += kh2x[k] * img[Ix[i + k - 1], j] -# end -# @inbounds img1[j, i] = tmp1 # Note the transpose -# @inbounds img2[j, i] = tmp2 -# end -# end - -# img12 = Array(Float64, sz) # Original image dims here -# img21 = Array(Float64, sz) -# for j in 1:sz[1] -# for i in 1:sz[2] -# tmp12, tmp21 = 0.0, 0.0 -# for k in 1:kernleny -# @inbounds tmp12 += kh2y[k] * img1[Iy[i + k - 1], j] -# @inbounds tmp21 += kh1y[k] * img2[Iy[i + k - 1], j] -# end -# @inbounds img12[j, i] = tmp12 # Transpose back to original dims -# @inbounds img21[j, i] = tmp21 -# end -# end -# copyproperties(img, img12 + img21) -# end - -# imfilter_LoG{T}(img::AbstractArray{T,2}, σ::Real, border="replicate") = -# imfilter_LoG(img::AbstractArray{T,2}, [σ, σ], border) - ### restrict, for reducing the image size by 2-fold # This properly anti-aliases. The only "oddity" is that edges tend towards zero under # repeated iteration. From 7394ee03f7b8ee23e31f33e9a904264c57b672dd Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 30 Jan 2017 06:02:32 -0600 Subject: [PATCH 38/38] Add tips for upgrading to new Images --- upgrading_tips.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 upgrading_tips.md diff --git a/upgrading_tips.md b/upgrading_tips.md new file mode 100644 index 00000000..2c5d7485 --- /dev/null +++ b/upgrading_tips.md @@ -0,0 +1,24 @@ +# Fixing errors: + +- Errors about properties like `timedim` should have self-explanatory + messages, but be aware that switching to the recommended `img = + AxisArray(A, :y, :x, :time)` may require other changes in your + code. For example, rather than `view(img, "t", t)` you should use + `tax = timeaxis(img); view(img, tax(t))`. (The new API has the + advantage of being inferrable and thus can be used in + performance-critical applications.) + +- `MethodError: no method matching getindex(::Tuple{Int64,Int64,Int64}, ::Tuple{Int64,Int64})` + in conjunction with `coords_spatial`: the return type is now a tuple + rather than an array. You can use `[coords_spatial(img)...]` to get + an array, although in some cases you may prefer to use the tuple + directly. For certain applications the tuple improves inferrability, + for example `size(img, coords_spatial(img)...)` now returns a tuple + of inferrable length, which is helpful for writing type-stable + algorithms. + +- `padarray` now returns arrays with unconventional indices: if you + pad a 1-dimensional array by 3 elements at the beginning and end, + the output's indices will be `-2:sz+3` rather than `1:sz+6`. If you + find it easier to work with arrays that start at 1, you can call + `parent` on the output.