Skip to content

Commit b81a492

Browse files
authored
Add a Matlab type Montage (#1896)
* Add getR function * Fix argument value * Extent test to not plot frame & annots for known image extensions * Let choropleths work also for text colorants * Add skeleton of a ML type montage function.
1 parent 9ab2a9b commit b81a492

6 files changed

Lines changed: 117 additions & 36 deletions

File tree

src/choropleth.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function extract_ids_vals(D, attrib::String)
132132
vals = deleteat!(vals, ind)
133133
ids = deleteat!(ids, ind)
134134
end
135-
ids, parse.(Float64, vals)
135+
ids, (tryparse(Float64, vals[1]) === nothing) ? collect(1.0:length(vals)) : parse.(Float64, vals)
136136
end
137137

138138
# ------------------------------------------------------------------------------------------------------

src/gdal_utils.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,20 @@ function getregion(input; pad=0, xSize=0, ySize=0, gridreg::Bool=false, sds::Int
515515
return x_min, x_max, y_min, y_max, x_inc, y_inc
516516
end
517517

518+
# ---------------------------------------------------------------------------------------------------
519+
"""
520+
opr_R = getR(X, full::Bool=false)::String
521+
522+
Return a string with the -R option made up of the region of 'X'.
523+
524+
`X` is a GMTimage, GMTgrid or GMTdataset(s). The `full` flag adds the " -R" prefix.
525+
"""
526+
function getR(X, full::Bool=false)::String
527+
x_min, x_max, y_min, y_max = getregion(X)
528+
pre = full ? " -R" : ""
529+
@sprintf("%s%f/%f/%f/%f", pre, x_min, x_max, y_min, y_max)
530+
end
531+
518532
# ---------------------------------------------------------------------------------------------------
519533
function coords_resque(dataset)
520534
# We come here when GDAL stuburnly finds a netcdf file indign and ignores its coordinates

src/gmtlogo.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function logo(cmd0::String=""; first=true, kwargs...)
5656
if (!occursin("-R", cmd)) cmd = @sprintf("-R0/%f/0/%f ", 2r, 2r) * cmd end
5757
if (!occursin("-J", cmd)) cmd = " -Jx1 " * cmd end
5858
if (do_GMTjulia) # Too soon to use the show, fmt, ...
59-
do_show, fmt, savefig = get_show_fmt_savefig(d, true) # Default is to show
59+
do_show, fmt, savefig = get_show_fmt_savefig(d, false) # Default is to !show
6060
end
6161
cmd = "psxy " * c * cmd
6262
((_r = check_dbg_print_cmd(d, cmd)) !== nothing && startswith(_r, "psxy")) && return _r

src/grdimage.jl

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function grdimage(cmd0::String="", arg1=nothing, arg2=nothing, arg3=nothing; fir
5050
(!haskey(d, :R) && any(startswith.(cmd0, ["@earth_", "@mars_", "@pluto_", "@moon_", "@venus_"]))) &&
5151
(CTRL.limits[1:4] = CTRL.limits[7:10] = [-180, 180, -90, 90])
5252

53-
if (arg1 === nothing && haskey(d, :R) && guess_T_from_ext(cmd0) == " -Ti")
53+
if (arg1 === nothing && (get(d, :R, nothing) !== nothing) && guess_T_from_ext(cmd0) == " -Ti")
5454
_opt_R = d[:R]
5555
t = (isa(_opt_R, Tuple) || isa(_opt_R, VMr)) ?
5656
["$(_opt_R[1])", "$(_opt_R[2])", "$(_opt_R[3])", "$(_opt_R[4])"] : split(_opt_R, '/')
@@ -77,22 +77,6 @@ end
7777
function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict)
7878

7979
arg4 = nothing # For the r,g,b + intensity case
80-
#=
81-
common_insert_R!(d, O, cmd0, arg1) # Set -R in 'd' out of grid/images (with coords) if limits was not used
82-
83-
# Remote files with no -R are all global. Set CTRL.limits so we can guess the projection.
84-
(!haskey(d, :R) && any(startswith.(cmd0, ["@earth_", "@mars_", "@pluto_", "@moon_", "@venus_"]))) &&
85-
(CTRL.limits[1:4] = CTRL.limits[7:10] = [-180, 180, -90, 90])
86-
87-
if (arg1 === nothing && haskey(d, :R) && guess_T_from_ext(cmd0) == " -Ti")
88-
_opt_R = d[:R]
89-
t = (isa(_opt_R, Tuple) || isa(_opt_R, VMr)) ?
90-
["$(_opt_R[1])", "$(_opt_R[2])", "$(_opt_R[3])", "$(_opt_R[4])"] : split(_opt_R, '/')
91-
opts = ["-projwin", t[1], t[4], t[2], t[3]] # -projwin <ulx> <uly> <lrx> <lry>
92-
arg1 = cut_with_gdal(cmd0, opts)
93-
cmd0 = ""
94-
end
95-
=#
9680

9781
# Prevent that J=guess is applied to a non-geog grid/image
9882
(arg1 !== nothing && (symb = is_in_dict(d, [:proj :projection])) !== nothing && (d[symb] == "guess" || d[symb] == :guess) && !isgeog(arg1)) &&
@@ -102,8 +86,11 @@ function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict)
10286
(is_in_dict(d, [:A :img_out :image_out]) !== nothing) && (d[:B] = "none") # When -A is used, -B is forbiden
10387
cmd::String, opt_B::String, opt_J::String, opt_R::String = parse_BJR(d, "", "", O, " -JX" * split(DEF_FIG_SIZE, '/')[1] * "/0")
10488
(startswith(opt_J, " -JX") && !contains(opt_J, "/")) && (cmd = replace(cmd, opt_J => opt_J * "/0")) # When sub-regions
105-
(!has_opt_B && isa(arg1, GMTimage) && (isimgsize(arg1) || CTRL.limits[1:4] == zeros(4)) && opt_B == DEF_FIG_AXES_BAK) &&
106-
(cmd = replace(cmd, opt_B => "")) # Dont plot axes for plain images if that was not required
89+
sim = (!has_opt_B && isa(arg1, GMTimage) && (isimgsize(arg1) || CTRL.limits[1:4] == zeros(4)) && opt_B == DEF_FIG_AXES_BAK)
90+
if (!sim && cmd0 !== "" && !has_opt_B)
91+
sim = (lowercase(splitext(cmd0)[2]) in [".jpg", ".jpeg", ".png", ".bmp", ".webp"])
92+
end
93+
sim && (cmd = replace(cmd, opt_B => ""))
10794

10895
cmd, = parse_common_opts(d, cmd, [:UVXY :params :margin :c :f :n :p :t]; first=!O)
10996
cmd = parse_these_opts(cmd, d, [[:A :img_out :image_out], [:D :img_in :image_in], [:E :dpi], [:G :bit_color],
@@ -122,21 +109,6 @@ function _grdimage(cmd0::String, arg1, arg2, arg3, O::Bool, K::Bool, d::Dict)
122109
end
123110
end
124111

125-
#=
126-
if (isa(arg1, Matrix{<:Real}) || isa(arg1, Array{<:Real,3}))
127-
if (isa(arg1, Matrix{UInt8}) || isa(arg1, Matrix{UInt16}) || isa(arg1, Array{UInt8,3}))
128-
arg1 = mat2img(arg1; d...)
129-
else
130-
arg1 = mat2grid(arg1)
131-
(isa(arg2, Matrix{<:Real})) && (arg2 = mat2grid(arg2))
132-
(isa(arg3, Matrix{<:Real})) && (arg3 = mat2grid(arg3))
133-
end
134-
elseif (isa(arg1, GMTimage) && size(arg1, 3) <= 3 && eltype(arg1.image) <: UInt16)
135-
arg1 = mat2img(arg1; d...)
136-
(haskey(d, :stretch) || haskey(d, :histo_bounds)) && delete!(d, [:histo_bounds, :stretch])
137-
end
138-
=#
139-
140112
set_defcpt!(d, cmd0, arg1) # When dealing with a remote grid assign it a default CPT
141113
(isa(arg1, GMTgrid) && arg1.cpt != "") && (d[:this_cpt] = arg1.cpt)
142114
(haskey(d, :this_cpt) && isfile(d[:this_cpt])) && (CURRENT_CPT[] = gmtread(d[:this_cpt]))

src/montage.jl

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
export montage
2+
3+
# This file was initially created by Claude and still needs lots of cleanings.
4+
5+
6+
# ------------------------------------------------------------------------------------------
7+
"""
8+
montage(images; kwargs...)
9+
10+
Display multiple images arranged in a grid using GMT's subplot.
11+
12+
### Arguments
13+
- `images`: Vector of GMTimage/GMTgrid objects, filenames, or a 3D/4D array.
14+
15+
### Keywords
16+
- `grid`: Tuple (nrows, ncols) specifying grid dimensions. Default: approximately square.
17+
- `panels_size`: Size of each panel in cm, e.g. `5` or `(5, 4)`. Default: auto.
18+
- `margin`: Margin between panels. Default: "0.1c"
19+
- `title`: Overall figure title.
20+
- `titles`: Vector of titles for each panel.
21+
- `frame`: Frame setting for panels. Default: :none
22+
- `indices`: Vector of indices selecting which images to display.
23+
- `show`: Display the result. Default: true.
24+
- `savefig`: Filename to save the montage.
25+
26+
### Example
27+
```julia
28+
# From files
29+
montage(["img1.png", "img2.png", "img3.png", "img4.png"], grid=(2,2))
30+
31+
# From GMTimage array with titles
32+
imgs = [gmtread("img\$i.png") for i in 1:6]
33+
montage(imgs, grid=(2,3), titles=["A","B","C","D","E","F"], panels_size=5)
34+
35+
# From 3D array
36+
montage(rand(UInt8, 64, 64, 9), grid=(3,3), margin="0.2c")
37+
```
38+
"""
39+
function montage(images; grid=nothing, panels_size=nothing, margin="0.0c",
40+
title=nothing, titles=nothing, frame=:none, indices=nothing,
41+
show::Bool=true, noR::Bool=false, kw...)
42+
43+
(indices !== nothing) && (images = images[indices]) # Apply indices selection
44+
((n = length(images)) == 0) && error("No images to display")
45+
nrows, ncols = _montage_grid_size(n, grid) # Calculate grid dimensions
46+
47+
# Build subplot options
48+
subplot_kw = Dict{Symbol, Any}(:grid => (nrows, ncols), :margin => margin, :frame => frame)
49+
50+
if panels_size !== nothing
51+
subplot_kw[:panels_size] = panels_size
52+
else # Auto size based on grid
53+
ps = max(3, min(8, 18 / max(nrows, ncols)))
54+
subplot_kw[:panels_size] = ps
55+
end
56+
57+
title !== nothing && (subplot_kw[:title] = title)
58+
59+
# Merge additional kwargs
60+
for (k, v) in kw
61+
subplot_kw[k] = v
62+
end
63+
subplot_kw[:Vd] = 1
64+
65+
subplot(; subplot_kw...) # Create subplot
66+
67+
# Plot each image
68+
k = 0
69+
for row in 1:nrows
70+
for col in 1:ncols
71+
k += 1
72+
panel_title = (titles !== nothing && k <= length(titles)) ? titles[k] : nothing
73+
opt_R = isa(images[k], GItype) ? (noR ? "" : getR(images[k])) : ""
74+
viz(images[k], panel=(row, col), title=panel_title, R=(opt_R !== "" ? opt_R : nothing), Vd=1, show=false)
75+
#grdimage(images[k], panel=(row, col), title=panel_title, R=(opt_R !== "" ? opt_R : nothing), Vd=1, show=false)
76+
end
77+
end
78+
subplot(show ? :show : :end) # End subplot
79+
80+
return nothing
81+
end
82+
83+
# Calculate grid dimensions
84+
function _montage_grid_size(n, size)
85+
if size === nothing || all(x -> x === nothing || x == 0 || (isa(x, AbstractFloat) && isnan(x)), size)
86+
ncols = ceil(Int, sqrt(n))
87+
nrows = ceil(Int, n / ncols)
88+
else
89+
nrows, ncols = size
90+
(nrows == 0 || (isa(nrows, AbstractFloat) && isnan(nrows))) && (nrows = ceil(Int, n / ncols))
91+
(ncols == 0 || (isa(ncols, AbstractFloat) && isnan(ncols))) && (ncols = ceil(Int, n / nrows))
92+
end
93+
return Int(nrows), Int(ncols)
94+
end

src/utils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,3 +1769,4 @@ end
17691769

17701770
include("getdcw.jl")
17711771
include("choropleth.jl")
1772+
include("montage.jl")

0 commit comments

Comments
 (0)