Warping an image to polar coordinates

Hi, I’m trying to map an image from cartesian coordinates to polar coordinates (i.e. have the center pixel along the left edge and the outside pixels along the right edge with some pixels warped from outside the image).

I think I can do this with ImageTransformations.jl’s warp & CoordinateTransformations.jl’s PolarFromCartesian() but I’m having trouble getting it working. My code is as follows:

using Images, FileIO, CoordinateTransformations, ImageTransformations

background_color = ones(UInt8, 3) * 255
img = rawview(channelview(load("img/lakspe184.png")))
energy = (background_color .- img) .^ 2
# energy_pol = warp(energy, PolarFromCartesian(), fill=maximum(energy))
energy_pol = warp(energy, PolarFromCartesian())

Which is giving me the following error:

ERROR: LoadError: MethodError: no method matching (::CartesianFromPolar)(::StaticArrays.SArray{Tuple{3},Int64,1,3})
Closest candidates are:
  Any(!Matched::Polar) at /home/hans/.julia/packages/CoordinateTransformations/1fp7i/src/coordinatesystems.jl:42
Stacktrace:
 [1] autorange(::CartesianIndices{3,Tuple{Base.OneTo{Int64},Base.OneTo{Int64},Base.OneTo{Int64}}}, ::CartesianFromPolar) at /home/hans/.julia/packages/ImageTransformations/jDWnD/src/autorange.jl:7
 [2] autorange(::Interpolations.FilledExtrapolation{Int64,3,Interpolations.BSplineInterpolation{Int64,3,Array{Int64,3},Interpolations.BSpline{Interpolations.Linear},Tuple{Base.OneTo{Int64},Base.OneTo{Int64},Base.OneTo{Int64}}},Interpolations.BSpline{Interpolations.Linear},Int64}, ::CartesianFromPolar) at /home/hans/.julia/packages/ImageTransformations/jDWnD/src/autorange.jl:3
 [3] warp(::Interpolations.FilledExtrapolation{Int64,3,Interpolations.BSplineInterpolation{Int64,3,Array{Int64,3},Interpolations.BSpline{Interpolations.Linear},Tuple{Base.OneTo{Int64},Base.OneTo{Int64},Base.OneTo{Int64}}},Interpolations.BSpline{Interpolations.Linear},Int64}, ::PolarFromCartesian) at /home/hans/.julia/packages/ImageTransformations/jDWnD/src/warp.jl:83
 [4] warp(::Array{Int64,3}, ::PolarFromCartesian) at /home/hans/.julia/packages/ImageTransformations/jDWnD/src/warp.jl:101
 [5] image_energy(::MappedArrays.MappedArray{UInt8,3,Base.ReinterpretArray{Normed{UInt8,8},3,RGB{Normed{UInt8,8}},Array{RGB{Normed{UInt8,8}},3}},typeof(reinterpret),ImageCore.var"##37#38"{Normed{UInt8,8}}}) at /home/hans/Documents/content-aware-center-crop/cacc.jl:10
 [6] top-level scope at /home/hans/Documents/content-aware-center-crop/cacc.jl:15
 [7] include at ./boot.jl:328 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1105
 [9] include(::Module, ::String) at ./Base.jl:31
 [10] exec_options(::Base.JLOptions) at ./client.jl:295
 [11] _start() at ./client.jl:468
in expression starting at /home/hans/Documents/content-aware-center-crop/cacc.jl:15

Can anyone explain what I’m doing wrong?

PS. The commented out line gives an error about warp() not accepting keyword args while the type signature from the docstring looks like this warp(img, tform, [indices], [degree = Linear()], [fill = NaN]) -> imgw. How can I supply the fill argument correctly?

You can dot it with GMT.jl
Pretend image is on geographical coordinates

using GMT
imshow("https://i.ytimg.com/vi/2n7UgwWGUeQ/maxresdefault.jpg",
             region=(-180,180,0,90), proj=:polar_azim, image_in=:r, frame=:none, name="dduck.jpg")

4 Likes

Hmmm OK I think GMT might be able to do what I want, but I’m not quite sure how to get that working.

First of all, when running the code “dduck.jpg” code I’m getting the following error:

grdimage [COMPATIBILITY]: Option -D is deprecated; images are detected automatically

signal (11): Segmentation fault
in expression starting at /home/hans/Documents/content-aware-center-crop/cacc.jl:48
GMT_grdimage at /usr/local/lib/libgmt.so (unknown line)
GMT_Call_Module at /usr/local/lib/libgmt.so (unknown line)
GMT_Call_Module at /home/hans/.julia/packages/GMT/Ba3R6/src/libgmt.jl:165
unknown function (ip: 0x7f6b957e67f8)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2130 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
gmt at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:236
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2136 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1631 [inlined]
jl_f__apply at /buildworker/worker/package_linux64/build/src/builtins.c:627
finish_PS_module at /home/hans/.julia/packages/GMT/Ba3R6/src/common_options.jl:2189
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2136 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
#grdimage#95 at /home/hans/.julia/packages/GMT/Ba3R6/src/grdimage.jl:88
#grdimage at ./none:0
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2136 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
#imshow#124 at /home/hans/.julia/packages/GMT/Ba3R6/src/imshow.jl:47
#imshow at ./none:0
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2136 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
jl_apply at /buildworker/worker/package_linux64/build/src/julia.h:1631 [inlined]
do_call at /buildworker/worker/package_linux64/build/src/interpreter.c:328
eval_value at /buildworker/worker/package_linux64/build/src/interpreter.c:417
eval_stmt_value at /buildworker/worker/package_linux64/build/src/interpreter.c:368 [inlined]
eval_body at /buildworker/worker/package_linux64/build/src/interpreter.c:778
jl_interpret_toplevel_thunk_callback at /buildworker/worker/package_linux64/build/src/interpreter.c:888
unknown function (ip: 0xfffffffffffffffe)
unknown function (ip: 0x7f6b899f440f)
unknown function (ip: 0x5)
jl_interpret_toplevel_thunk at /buildworker/worker/package_linux64/build/src/interpreter.c:897
jl_toplevel_eval_flex at /buildworker/worker/package_linux64/build/src/toplevel.c:814
jl_parse_eval_all at /buildworker/worker/package_linux64/build/src/ast.c:873
jl_load at /buildworker/worker/package_linux64/build/src/toplevel.c:878
include at ./boot.jl:328 [inlined]
include_relative at ./loading.jl:1105
include at ./Base.jl:31
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2130 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
exec_options at ./client.jl:295
_start at ./client.jl:468
jfptr__start_2083.clone_1 at /opt/julia-1.3.0-rc2/lib/julia/sys.so (unknown line)
_jl_invoke at /buildworker/worker/package_linux64/build/src/gf.c:2130 [inlined]
jl_apply_generic at /buildworker/worker/package_linux64/build/src/gf.c:2300
unknown function (ip: 0x401931)
unknown function (ip: 0x401533)
__libc_start_main at /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
unknown function (ip: 0x4015d4)
Allocations: 119946010 (Pool: 119926770; Big: 19240); GC: 98
[1]    10595 segmentation fault (core dumped)  julia cacc.jl

Which I guess is related to the libgmt.so file? I built it from source, but I’m using julia v1.3rc2 which might have some issues when using GMT.jl, maybe…

Also when I try to feed this function my own image I’m getting:

signal (11): Segmentation fault
in expression starting at /home/hans/Documents/content-aware-center-crop/cacc.jl:48
gmt_strtok at /usr/local/lib/libgmt.so (unknown line)
GMT_Create_Options at /usr/local/lib/libgmt.so (unknown line)
unknown function (ip: 0x3130302e302f3535)
Allocations: 124433585 (Pool: 124414263; Big: 19322); GC: 102
[1]    11930 segmentation fault (core dumped)  julia cacc.jl

Sorry, what command are you trying to run? dduck.jpg is the end result (the polar image). I see a call to cacc.jl but that is not part of GMT.jl

To display the image you need also ghostscript installed (the install page warns about that). What you need to run is the command that I showed. Without a ghostscript installation, this variation will create a file called GMTjl_tmp.ps in your tmp directory. But then you need something that displays it.

imshow("https://i.ytimg.com/vi/2n7UgwWGUeQ/maxresdefault.jpg",
       region=(-180,180,0,90), proj=:polar_azim, image_in=:r, frame=:none, show=false)
1 Like

Sorry I was unclear, cacc.jl is the file I’m writing. It looks like this:

using Images, GMT

background_color = ones(UInt8, 3) * 255
img = rawview(channelview(load("img/lakspe184.png")))
energy = dropdims(sum((background_color .- img) .^ 2, dims=1), dims=1)
# imshow("https://i.ytimg.com/vi/2n7UgwWGUeQ/maxresdefault.jpg", region=(-180,180,0,90), proj=:polar_azim, image_in=:r, frame=:none, show=false)
# imshow(energy/maximum(energy), region=(-180,180,0,90), proj=:polar_azim, image_in=:r, frame=:none, show=false)
imshow(energy/maximum(energy), region=(-180,180,0,90), proj=:polar_azim, image_in=energy/maximum(energy), frame=:none, show=false)

The first imshow gives the first of the segfault errors from my previous post, the second gives an error about the input not being an image, and the third imshow gives the second segfault error.

I am gettting the warped daffy duck GMTjl_tmp.ps in my /tmp directory with the first imshow.

I’ve tried it out with julia 1.2 but that didn’t make a difference.

Also this stackoverflow post illustrates exactly what I’m trying to do. I’ve seen there’s an ImageMagick.jl, but looking at it seems like it’s only a wrapper around the IO functions. Is there any way to do the DePolar distort mentioned on stackoverflow with ImageMagick.jl?

I’m still confused. You say that the first imshow gives the segfault but that it still creates the warped GMTjl_tmp.ps in your /tmp directory?

Some details. What GNT produces are postscript files and when we do specify a name=imgname.xxx there is an under the hood call to a GMT module (psconvert) that converts the ps to the format specified. So if the imshow command works for you (no crash) without the name=imgname.xxx but crashes when using it, then it means the problem is the call to ghostscript.

Your second call could probably be made to work but not the third. In fact I have to make the parsing of that keyword more robust.

Alright so it seems it was related to the sources I was using to build GMT. I rebuilt from github.com/GenericMappingTools/gmt (instead of svn://gmtserver.soest.hawaii.edu/gmt5/trunk) and now running your dduck imshow command works properly!

Now I’m trying to apply it to my own array, but getting some errors.

using Images, GMT

background_color = ones(UInt8, 3) * 255
img = rawview(channelview(load("img/lakspe184.png")))
energy = dropdims(sum((background_color .- img) .^ 2, dims=1), dims=1)

# imshow(energy/maximum(energy), region=(-180,180,0,90),
#        proj=:polar_azim, image_in=:r, frame=:none)

energy_pol = grdimage(energy/maximum(energy))
imshow(energy_pol, region=(-180,180,0,90), proj=:polar_azim)

The imshow command gives me:

ERROR: LoadError: image_init: input is not a IMAGE container type
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] image_init(::Ptr{Nothing}, ::Bool, ::GMT.GMTgrid, ::UInt32) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:928
 [3] GMTJL_Set_Object(::Ptr{Nothing}, ::GMT.GMT_RESOURCE, ::GMT.GMTgrid) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:779
 [4] gmt(::String, ::GMT.GMTgrid, ::Vararg{Any,N} where N) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:232
 [5] finish_PS_module(::Dict{Symbol,Any}, ::String, ::String, ::String, ::String, ::String, ::Bool, ::Bool, ::Bool, ::GMT.GMTgrid, ::Vararg{Any,N} where N) at /home/hans/.julia/packages/GMT/Ba3R6/src/common_options.jl:2189
 [6] #grdimage#95(::Bool, ::Base.Iterators.Pairs{Symbol,Any,NTuple{5,Symbol},NamedTuple{(:show, :region, :proj, :image_in, :frame),Tuple{Bool,NTuple{4,Int64},Symbol,Symbol,Symbol}}}, ::typeof(grdimage), ::String, ::GMT.GMTgrid, ::Nothing, ::Nothing) at /home/hans/.julia/packages/GMT/Ba3R6/src/grdimage.jl:88
 [7] (::GMT.var"#kw##grdimage")(::NamedTuple{(:first, :show, :region, :proj, :image_in, :frame),Tuple{Bool,Bool,NTuple{4,Int64},Symbol,Symbol,Symbol}}, ::typeof(grdimage), ::GMT.GMTgrid) at ./none:0
 [8] #imshow#124(::Bool, ::Base.Iterators.Pairs{Symbol,Any,NTuple{5,Symbol},NamedTuple{(:region, :proj, :image_in, :frame, :show),Tuple{NTuple{4,Int64},Symbol,Symbol,Symbol,Bool}}}, ::typeof(imshow), ::Array{Float64,2}) at /home/hans/.julia/packages/GMT/Ba3R6/src/imshow.jl:52
 [9] (::GMT.var"#kw##imshow")(::NamedTuple{(:region, :proj, :image_in, :frame, :show),Tuple{NTuple{4,Int64},Symbol,Symbol,Symbol,Bool}}, ::typeof(imshow), ::Array{Float64,2}) at ./none:0
 [10] top-level scope at /home/hans/Documents/content-aware-center-crop/cacc.jl:28
 [11] include at ./boot.jl:328 [inlined]
 [12] include_relative(::Module, ::String) at ./loading.jl:1105
 [13] include(::Module, ::String) at ./Base.jl:31
 [14] exec_options(::Base.JLOptions) at ./client.jl:295
 [15] _start() at ./client.jl:468
in expression starting at /home/hans/Documents/content-aware-center-crop/cacc.jl:28

When I try to convert it to a grdimage first I’m getting an error related to the color palette:

ERROR: LoadError: get_palette: programming error, output CPT is empty
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] get_palette(::Ptr{Nothing}, ::Ptr{Nothing}) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:553
 [3] GMTJL_Get_Object(::Ptr{Nothing}, ::GMT.GMT_RESOURCE) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:831
 [4] gmt(::String, ::Nothing) at /home/hans/.julia/packages/GMT/Ba3R6/src/gmt_main.jl:257
 [5] monolitic at /home/hans/.julia/packages/GMT/Ba3R6/src/common_options.jl:2377 [inlined]
 [6] #makecpt#129 at /home/hans/.julia/packages/GMT/Ba3R6/src/makecpt.jl:70 [inlined]
 [7] makecpt at /home/hans/.julia/packages/GMT/Ba3R6/src/makecpt.jl:70 [inlined] (repeats 2 times)
 [8] add_opt_cpt(::Dict{Symbol,Any}, ::String, ::Array{Symbol,2}, ::Char, ::Int64, ::GMT.GMTgrid, ::Nothing, ::Bool, ::Bool, ::String, ::Bool) at /home/hans/.julia/packages/GMT/Ba3R6/src/common_options.jl:1119
 [9] get_cpt_set_R(::Dict{Symbol,Any}, ::String, ::String, ::String, ::Int64, ::GMT.GMTgrid, ::Nothing, ::Nothing, ::String) at /home/hans/.julia/packages/GMT/Ba3R6/src/common_options.jl:1199
 [10] #grdimage#95(::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(grdimage), ::String, ::Array{Float64,2}, ::Nothing, ::Nothing) at /home/hans/.julia/packages/GMT/Ba3R6/src/grdimage.jl:80
 [11] #grdimage at ./none:0 [inlined]
 [12] #grdimage#97 at /home/hans/.julia/packages/GMT/Ba3R6/src/grdimage.jl:121 [inlined]
 [13] grdimage at /home/hans/.julia/packages/GMT/Ba3R6/src/grdimage.jl:121 [inlined] (repeats 2 times)
 [14] top-level scope at /home/hans/Documents/content-aware-center-crop/cacc.jl:30
 [15] include at ./boot.jl:328 [inlined]
 [16] include_relative(::Module, ::String) at ./loading.jl:1105
 [17] include(::Module, ::String) at ./Base.jl:31
 [18] exec_options(::Base.JLOptions) at ./client.jl:295
 [19] _start() at ./client.jl:468
in expression starting at /home/hans/Documents/content-aware-center-crop/cacc.jl:30

Thanks a lot for your help!

Good that it kind of works now. It had to work because all ~900 CI tests run in a Linux machine.

I have no time this afternoon (here) but I’ll try later tonight. I’m convinced that converting your raw image into a GMTimage (that’s what it needs) can be done. But the other problem is that this command only does the Cartesian → Polar transform, not the inverse which is what you are looking for. It’s not hopeless but I have to check if can assign a projection reference (saying it’s polar) to the original image and ask to do an inverse projection. This is also the same type of thing one would have to do with GDAL (and it’s module gdalwarp).

Sorry, got no solution for the inverse projection. GMT is a package much more specialized on grids then images (reprojecting a grid from polar to cartesian should work, but there is no equivalent for images). However, GDAL should do it but I don’t understand why the following command does not reproject correctly an example image from your SO link that I warped to Polar Sterographic and converted to geotiff. Unfortunately Github does not allow me to upload the file because it says the format is not authorized.

gdalwarp -t_srs +proj=eqc lines_PS.tiff cart.tiff

Hi,

Did you solve your issue? I have the ~same here