diff --git a/.travis.yml b/.travis.yml index d6edc08..ce0debb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ os: - osx julia: - release - - nightly + - 0.6 notifications: email: false # uncomment the following lines to override the default test script @@ -13,4 +13,4 @@ notifications: # - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi # - julia -e 'Pkg.clone(pwd()); Pkg.build("Pyramids"); Pkg.test("Pyramids"; coverage=true)' after_success: - - julia -e 'cd(Pkg.dir("Pyramids")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())' \ No newline at end of file + - julia -e 'cd(Pkg.dir("Pyramids")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())' diff --git a/README.md b/README.md index 70cf0c2..df43380 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To install and begin using Pyramids, run the following in Julia: While parts of the Pyramids library are adapted from [matlabPyrTools](http://www.cns.nyu.edu/lcv/software.php), use of the library is quite different. For a more direct port of matlabPyrTools, look at [juliaPyrTools](https://github.com/rcrandall/JuliaPyrTools). -The Pyramids library is used through the `ImagePyramid` class. To create a new `ImagePyramid`, there a several possible constructors. Most typically, they take the form `ImagePyramid(im::Image, t::PyramidType)`. +The Pyramids library is used through the `ImagePyramid` class. To create a new `ImagePyramid`, there a several possible constructors. Most typically, they take the form `ImagePyramid(im::Array, t::PyramidType)`. Subtypes of `PyramidType` include: * The abstract type `SimplePyramid` @@ -35,11 +35,9 @@ Below is an example of loading an image and converting it to an `ImagePyramid`. using Images, Pyramids - im = load("test_im.png") + im = real.(load("cameraman.png")) pyramid = ImagePyramid(im, ComplexSteerablePyramid(), scale=0.75, max_levels=23) -An `ImagePyramid` may also be created directly from an `Array`. - To manipulate an `ImagePyramid`, the `subband` and `update_subband` functions may be used. hi_residual = subband(pyramid, 0) diff --git a/REQUIRE b/REQUIRE index 8d6786b..0971785 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ -julia 0.4 -Images 0.5 -Interpolations 0.3 -Colors 0.6 +julia 0.6 +Images 0.12 +Interpolations 0.7 +Colors 0.8 diff --git a/appveyor.yml b/appveyor.yml index c4f7c0d..17642e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6.2-latest-win32.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6.2-latest-win64.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" diff --git a/examples/frame_interpolation.jl b/examples/frame_interpolation.jl index 23f83df..d20dd69 100644 --- a/examples/frame_interpolation.jl +++ b/examples/frame_interpolation.jl @@ -54,7 +54,7 @@ function shift_correction(phi_delta::ImagePyramid; shift_limit=0.5) end end else - phi_l[abs(phi_l) .> phi_limit] = 0 + phi_l[abs.(phi_l) .> phi_limit] = 0 end update_subband!(corrected_phase_delta, level, phi_l, orientation=orientation) @@ -100,7 +100,7 @@ function blend_and_interpolate(pyramid1::ImagePyramid, pyramid2::ImagePyramid, p for l = 1:pyramid1.num_levels for o = 1:pyramid1.num_orientations - new_band = ((1 - alpha) * abs(subband(pyramid1, l, orientation=o)) + alpha * abs(subband(pyramid2, l, orientation=o))) .* exp(complex(0,1) * (angle(subband(pyramid1, l, orientation=o)) + alpha * subband(phase_delta, l, orientation=o))) + new_band = ((1 - alpha) * abs.(subband(pyramid1, l, orientation=o)) + alpha * abs.(subband(pyramid2, l, orientation=o))) .* exp(complex(0,1) * (angle.(subband(pyramid1, l, orientation=o)) + alpha * subband(phase_delta, l, orientation=o))) update_subband!(blended_pyramid, l, new_band, orientation=o) end end @@ -122,13 +122,13 @@ end function phase_difference(pyramid1::ImagePyramid, pyramid2::ImagePyramid) phase_bands = Dict{Int, Union{Array,Dict{Int, Array}}}() - phase_bands[0] = angle(subband(pyramid2, 0)) - angle(subband(pyramid1, 0)) + phase_bands[0] = angle.(subband(pyramid2, 0)) - angle.(subband(pyramid1, 0)) for l = 1:pyramid1.num_levels this_band = Dict{Int, Array}() for o = 1:pyramid1.num_orientations - this_band[o] = angle(subband(pyramid2, l, orientation=o)) - angle(subband(pyramid1, l, orientation=o)) + this_band[o] = angle.(subband(pyramid2, l, orientation=o)) - angle.(subband(pyramid1, l, orientation=o)) end phase_bands[l] = copy(this_band) @@ -139,17 +139,17 @@ end println("Loading images") +# convert to LAB, extract the L channel as a 1D array and process it +# update documentation + im1 = load("frame_0.jpg") -im1 = convert(Image{Lab}, float32(im1)) +im1 = channelview(Lab.(im1)) im2 = load("frame_1.jpg") -im2 = convert(Image{Lab}, float32(im2)) - -im1 = convert(Array, separate(im1)) -im2 = convert(Array, separate(im2)) +im2 = channelview(Lab.(im2)) -L1 = im1[:,:,1] -L2 = im2[:,:,1] +L1 = im1[1,:,:] +L2 = im2[1,:,:] println("Converting images to complex steerable pyramids") @@ -166,12 +166,12 @@ for alpha = 0:0.2:1.0 newim = toimage(newpyr) newLabIm = zeros(im1) - newLabIm[:,:,1] = newim - newLabIm[:,:,2] = (1 - alpha) * im1[:,:,2] + alpha * im2[:,:,2] - newLabIm[:,:,3] = (1 - alpha) * im1[:,:,3] + alpha * im2[:,:,3] - - newLabIm = convert(Image, newLabIm) - newLabIm.properties["colorspace"] = "Lab" - newLabIm = convert(Image{RGB}, newLabIm) - save("interpolated_frame_$(alpha).png", newLabIm') -end \ No newline at end of file + newLabIm[1,:,:] = newim + newLabIm[2,:,:] = (1 - alpha) * im1[2,:,:] + alpha * im2[2,:,:] + newLabIm[3,:,:] = (1 - alpha) * im1[3,:,:] + alpha * im2[3,:,:] + + newLabIm = colorview(Lab, newLabIm) + rgbIm = RGB.(newLabIm) + save("interpolated_frame_$(alpha).png", rgbIm) +end + diff --git a/src/Pyramids.jl b/src/Pyramids.jl index 553b782..5d5fd66 100644 --- a/src/Pyramids.jl +++ b/src/Pyramids.jl @@ -5,9 +5,9 @@ using Images, Interpolations, Colors export ImagePyramid, PyramidType, ComplexSteerablePyramid, LaplacianPyramid, GaussianPyramid export subband, toimage, update_subband, update_subband!, test -"Abstract supertype for the variety of pyramids that can be constructed." -abstract PyramidType -abstract SimplePyramid <: PyramidType +"abs.tract supertype for the variety of pyramids that can be constructed." +abstract type PyramidType end +abstract type SimplePyramid <: PyramidType end """Type that indicates a complex steerable pyramid. [1] @@ -54,7 +54,7 @@ type ImagePyramid h = im_dims[1] w = im_dims[2] - this.num_levels = min(ceil(log2(minimum([h w]))/log2(1/scale) - (log2(min_size)/log2(1/scale))),max_levels); + this.num_levels = min(ceil.(log2(minimum([h w]))/log2(1/scale) - (log2(min_size)/log2(1/scale))),max_levels); pyramid_bands, mtx, harmonics = build_complex_steerable_pyramid(im, this.num_levels, this.num_levels, order=num_orientations-1, twidth=twidth, scale=scale) @@ -63,12 +63,6 @@ type ImagePyramid return this end - function ImagePyramid(im::Image, t::ComplexSteerablePyramid; scale=0.5, min_size=15, num_orientations=8, max_levels=23, twidth=1) - im_arr = image_to_array(im) - - return ImagePyramid(im_arr, t, scale=scale, min_size=min_size, num_orientations=num_orientations, max_levels=23, twidth=1) - end - function ImagePyramid(im::Array, t::GaussianPyramid; min_size=15, max_levels=23, filter=[0.0625; 0.25; 0.375; 0.25; 0.0625]) this = new() @@ -97,12 +91,6 @@ type ImagePyramid return this end - function ImagePyramid(im::Image, t::SimplePyramid; min_size=15, max_levels=23, filter=[0.0625; 0.25; 0.375; 0.25; 0.0625]) - im_arr = image_to_array(im) - - return ImagePyramid(im_arr, t, min_size=min_size, max_levels=max_levels, filter=filter) - end - function ImagePyramid(pyramid_bands::Dict, scale, t, num_levels, num_orientations) this = new() @@ -164,22 +152,6 @@ function toimage(pyramid::ImagePyramid) return im end -############################## -# Private helper functions. - -function image_to_array(im::Image) - # Please someone tell me a better way of checking for image type. - if typeof(im.data[1]) <: Gray - im_arr = separate(convert(Array{Float64,2}, im)) - else - warn("ImagePyramid only supports 2D images currently. Returning luminance channel.") - im_lab = convert(Image{Lab}, im) - im_arr = convert(Array{Float64,3}, separate(im))[:,:,1] - end - - return im_arr -end - ############################## # Private functions for building Gaussian and Laplacian pyramids. @@ -221,7 +193,7 @@ function generate_gaussian_pyramid(im; min_size=15, max_levels=23, filter=[0.062 pyramid_bands = Dict{Integer, Array}() im_dims = collect(size(im)) - num_levels = min(max_levels, ceil(Int, log2(minimum(im_dims)) - log2(min_size))) + num_levels = min(max_levels, ceil.(Int, log2(minimum(im_dims)) - log2(min_size))) for i = 1:num_levels pyramid_bands[i-1] = im @@ -238,7 +210,7 @@ function generate_laplacian_pyramid(im; min_size=15, max_levels=23, filter=[0.06 im_dims = collect(size(im)) - num_levels = min(max_levels, ceil(Int, log2(minimum(im_dims)) - log2(min_size))) + num_levels = min(max_levels, ceil.(Int, log2(minimum(im_dims)) - log2(min_size))) filter_offset::Int = (length(filter)-1)/2 @@ -283,12 +255,12 @@ function construct_steering_matrix(harmonics, angles; even = true) imtx[:,col] = ones(angles) col += 1 elseif !even - imtx[:,col] = sin(args) - imtx[:,col+1] = -cos(args) + imtx[:,col] = sin.(args) + imtx[:,col+1] = -cos.(args) col += 2 else - imtx[:,col] = cos(args) - imtx[:,col+1] = sin(args) + imtx[:,col] = cos.(args) + imtx[:,col+1] = sin.(args) col += 2 end end @@ -305,7 +277,7 @@ end function raisedcosine(width=1, position=0, values=[0,1]) sz = 256 # arbitrary X = pi * (-sz-1:1) / (2 * sz) - Y = values[1] + (values[2]-values[1]) * cos(X).^2 + Y = values[1] + (values[2]-values[1]) * cos.(X).^2 Y[1] = Y[2] Y[sz+3] = Y[sz+2] @@ -320,7 +292,7 @@ function build_complex_steerable_pyramid(im, height, nScales; order=3, twidth=1, num_orientations = order + 1 # generate steering matrix and harmonics info - if mod(num_orientations, 2) == 0 + if mod.(num_orientations, 2) == 0 harmonics = ((0:((num_orientations/2)-1))*2 + 1)' else harmonics = ((0:((num_orientations-1)/2))*2)' @@ -330,15 +302,15 @@ function build_complex_steerable_pyramid(im, height, nScales; order=3, twidth=1, imdft = fftshift(fft(im)) im_dims = collect(size(im)) - ctr = ceil(Int, (im_dims+0.5)/2) + ctr = ceil.(Int, (im_dims+0.5)/2) angle = broadcast(atan2, (((1:im_dims[1]) - ctr[1]) ./ (im_dims[1]/2)), (((1:im_dims[2]) - ctr[2]) ./ (im_dims[2]/2))') #SLOOW - log_rad = broadcast((x,y) -> log2(sqrt(x.^2 + y.^2)), (((1:im_dims[1]) - ctr[1]) ./ (im_dims[1]/2)), (((1:im_dims[2]) - ctr[2]) ./ (im_dims[2]/2))') #SLOOW + log_rad = broadcast((x,y) -> log2(sqrt.(x.^2 + y.^2)), (((1:im_dims[1]) - ctr[1]) ./ (im_dims[1]/2)), (((1:im_dims[2]) - ctr[2]) ./ (im_dims[2]/2))') #SLOOW log_rad[ctr[1], ctr[2]] = log_rad[ctr[1], ctr[2]-1] Xrcos, Yrcos = raisedcosine(twidth, (-twidth/2), [0 1]) - Yrcos = sqrt(Yrcos) - YIrcos = sqrt(1 - Yrcos.^2) + Yrcos = sqrt.(Yrcos) + YIrcos = sqrt.(1 - Yrcos.^2) # generate high frequency residual Yrcosinterpolant = interpolate((Xrcos,), Yrcos, Gridded(Linear())) @@ -369,7 +341,7 @@ function build_complex_steerable_pyramid(im, height, nScales; order=3, twidth=1, for iter in eachindex(lo0dft) ang = angle[iter]-pi*(b-1)/num_orientations - a = (abs(mod(pi+ang, 2*pi) - pi) .< pi/2) .* (2*sqrt(cnst) * (cos(ang).^order)) + a = (abs.(mod.(pi+ang, 2*pi) - pi) .< pi/2) .* (2*sqrt.(cnst) * (cos(ang).^order)) banddft[iter] = ((complex(0,-1)).^(num_orientations-1)) * lo0dft[iter] * himask[iter] * a end @@ -379,18 +351,18 @@ function build_complex_steerable_pyramid(im, height, nScales; order=3, twidth=1, pyramid_bands[height-ht+1] = pyramid_level dims = collect(size(lo0dft)) - ctr = ceil(Int, (dims+0.5)/2) + ctr = ceil.(Int, (dims+0.5)/2) - lodims = round(Int, im_dims[1:2]*scale^(nScales-ht+1)) + lodims = round.(Int, im_dims[1:2]*scale^(nScales-ht+1)) - loctr = ceil(Int, (lodims+0.5)/2) + loctr = ceil.(Int, (lodims+0.5)/2) lostart = ctr - loctr+1 loend = lostart + lodims - 1 log_rad = log_rad[lostart[1]:loend[1], lostart[2]:loend[2]] angle = angle[lostart[1]:loend[1], lostart[2]:loend[2]] lodft = lo0dft[lostart[1]:loend[1], lostart[2]:loend[2]] - YIrcos = abs(sqrt(1 - Yrcos.^2)) + YIrcos = abs.(sqrt.(1 - Yrcos.^2)) YIrcosinterpolant = interpolate((Xrcos,), YIrcos, Gridded(Linear())) lomask = reshape(YIrcosinterpolant[log_rad], size(log_rad)) @@ -412,12 +384,12 @@ function make_angle_grid(sz, phase=0, origin=-1) origin = (sz + 1)/2 end - xramp = ones(round(Int, sz[1]), 1) * collect((1:sz[2]) - origin[2])' - yramp = collect((1:sz[1]) - origin[1]) * ones(1, round(Int, sz[2])) + xramp = ones(round.(Int, sz[1]), 1) * collect((1:sz[2]) - origin[2])' + yramp = collect((1:sz[1]) - origin[1]) * ones(1, round.(Int, sz[2])) - res = atan2(yramp, xramp) + res = atan2.(yramp, xramp) - res = mod(res+(pi-phase), 2*pi) - pi + res = mod.(res+(pi-phase), 2*pi) - pi return res end @@ -426,23 +398,23 @@ function reconstruct_steerable_pyramid(pyr::ImagePyramid; levs="all", bands="all dims = collect(size(subband(pyr, 0))) im_dft = zeros(Complex{Float64}, size(subband(pyr, 0))) - ctr = ceil(Int, (dims + 0.5)/2) + ctr = ceil.(Int, (dims + 0.5)/2) angle = broadcast(atan2, (((1:dims[1]) - ctr[1]) ./ (dims[1]/2)), (((1:dims[2]) - ctr[2]) ./ (dims[2]/2))') #SLOOW - log_rad = broadcast((x,y) -> log2(sqrt(x.^2 + y.^2)), (((1:dims[1]) - ctr[1]) ./ (dims[1]/2)), (((1:dims[2]) - ctr[2]) ./ (dims[2]/2))') #SLOOW + log_rad = broadcast((x,y) -> log2(sqrt.(x.^2 + y.^2)), (((1:dims[1]) - ctr[1]) ./ (dims[1]/2)), (((1:dims[2]) - ctr[2]) ./ (dims[2]/2))') #SLOOW log_rad[ctr[1], ctr[2]] = log_rad[ctr[1], ctr[2]-1] log_rad0 = log_rad angle0 = angle Xrcos, Yrcos = raisedcosine(twidth, (-twidth/2), [0 1]) - Yrcos = sqrt(Yrcos) - YIrcos = sqrt(1 - Yrcos.^2) + Yrcos = sqrt.(Yrcos) + YIrcos = sqrt.(1 - Yrcos.^2) # Start with low frequency residual low_dft = fftshift(fft(subband(pyr, pyr.num_levels+1))) lodims = collect(size(low_dft)) - loctr = ceil(Int, (lodims+0.5)/2) + loctr = ceil.(Int, (lodims+0.5)/2) lostart = ctr - loctr+1 loend = lostart + lodims - 1 @@ -458,7 +430,7 @@ function reconstruct_steerable_pyramid(pyr::ImagePyramid; levs="all", bands="all # Accumulate mid-bamds for level = pyr.num_levels:-1:1 lodims = collect(size(subband(pyr, level, orientation=1))) - loctr = ceil(Int, (lodims+0.5)/2) + loctr = ceil.(Int, (lodims+0.5)/2) lostart = ctr - loctr+1 loend = lostart + lodims - 1 @@ -471,12 +443,12 @@ function reconstruct_steerable_pyramid(pyr::ImagePyramid; levs="all", bands="all Xrcos = Xrcos + log2(1/scale) order = pyr.num_orientations - 1 - cnst = ((complex(0,1)).^(pyr.num_orientations-1)) * sqrt((2^(2*order))*(factorial(order)^2)/(pyr.num_orientations*factorial(2*order))) + cnst = ((complex(0,1)).^(pyr.num_orientations-1)) * sqrt.((2^(2*order))*(factorial(order)^2)/(pyr.num_orientations*factorial(2*order))) for orientation = 1:pyr.num_orientations band_dft = fftshift(fft(subband(pyr, level, orientation=orientation))) ang = angle-pi*(orientation-1)/pyr.num_orientations - angle_mask = 2 *(abs(mod(pi+ang, 2*pi) - pi) .< pi/2) .* (cnst * (cos(ang).^order)) + angle_mask = 2 *(abs.(mod.(pi+ang, 2*pi) - pi) .< pi/2) .* (cnst * (cos.(ang).^order)) im_dft[lostart[1]:loend[1], lostart[2]:loend[2]] += band_dft .* himask .* angle_mask end @@ -504,7 +476,7 @@ function convert_complex_steerable_pyramid_to_real(pyramid::ImagePyramid; levs=" for nsc in 1:num_levels dims = collect(size(subband(pyramid, nsc, orientation=1))) - ctr = ceil(Int, (dims+0.5)/2) + ctr = ceil.(Int, (dims+0.5)/2) ang = make_angle_grid(dims, 0, ctr) ang[ctr[1], ctr[2]] = -pi/2 @@ -512,10 +484,10 @@ function convert_complex_steerable_pyramid_to_real(pyramid::ImagePyramid; levs=" ch = subband(pyramid, nsc, orientation=nor) ang0 = pi*(nor-1)/num_orientations - xang = mod(ang-ang0+pi, 2*pi) - pi + xang = mod.(ang-ang0+pi, 2*pi) - pi # this creates an angular mask - amask = 2*(abs(xang) .< pi/2) + (abs(xang) .== pi/2) + amask = 2*(abs.(xang) .< pi/2) + (abs.(xang) .== pi/2) amask[ctr[1], ctr[2]] = 1 amask[:,1] = 1 amask[1,:] = 1 diff --git a/test/runtests.jl b/test/runtests.jl index c60bbad..54375b8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,13 +1,7 @@ using Pyramids using Base.Test, Images, Colors -function end_to_end(T; convert_to_arr=true) - test_im = rand(128, 128) - - if !convert_to_arr - test_im = convert(Image{Gray}, test_im) - end - +function end_to_end(test_im, T) if typeof(T) <: ComplexSteerablePyramid scale = 0.5^(1/4); pyramid = ImagePyramid(test_im, T, scale=scale, num_orientations=8, max_levels=23, min_size=15, twidth=1.0) @@ -20,18 +14,16 @@ function end_to_end(T; convert_to_arr=true) return all((test_im_recon .- test_im) .< 0.0002) end +rand_im = rand(128, 128) + println("Running end-to-end image comparison test for Complex Steerable Pyramid.") -@test end_to_end(ComplexSteerablePyramid()) -println("\twithout array conversion") -@test end_to_end(ComplexSteerablePyramid(), convert_to_arr=false) +@test end_to_end(rand_im, ComplexSteerablePyramid()) println("Running end-to-end image comparison test for Gaussian Pyramid.") -@test end_to_end(GaussianPyramid()) -println("\twithout array conversion") -@test end_to_end(GaussianPyramid(), convert_to_arr=false) +@test end_to_end(rand_im, GaussianPyramid()) println("Running end-to-end image comparison test for Laplacian Pyramid.") -@test end_to_end(LaplacianPyramid()) +@test end_to_end(rand_im, LaplacianPyramid()) # TODO: # Test pyramid copying