From 30a9ad0477cf926b4f1b60dfe449c79b54497adc Mon Sep 17 00:00:00 2001 From: mloubout Date: Mon, 28 Oct 2024 14:40:46 -0400 Subject: [PATCH] switch to backend-based --- .github/workflows/docs.yml | 1 + CondaPkg.toml | 6 --- Project.toml | 18 ++++--- deps/build.jl | 14 ------ examples/plot_example.jl | 27 ++++++----- ext/PyPlotSlimExt.jl | 35 ++++++++++++++ ext/PythonPlotSlimExt.jl | 34 ++++++++++++++ src/SlimPlotting.jl | 96 +++++++++++++++++++++++--------------- 8 files changed, 153 insertions(+), 78 deletions(-) delete mode 100644 CondaPkg.toml delete mode 100644 deps/build.jl create mode 100644 ext/PyPlotSlimExt.jl create mode 100644 ext/PythonPlotSlimExt.jl diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b7ca303..5ae9590 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,6 +23,7 @@ jobs: - name: Install dependencies run: | julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + julia -e 'using Pkg; Pkg.add("PythonPlot")' - name: Setup README run: cp README.md docs/src/README.md diff --git a/CondaPkg.toml b/CondaPkg.toml deleted file mode 100644 index 2b043ca..0000000 --- a/CondaPkg.toml +++ /dev/null @@ -1,6 +0,0 @@ -[deps] -colorcet = "" - -[pip.deps] -# Conda package names and versions -seiscm = "" \ No newline at end of file diff --git a/Project.toml b/Project.toml index e66972c..f2a760d 100644 --- a/Project.toml +++ b/Project.toml @@ -5,14 +5,20 @@ version = "1.0.0" [deps] ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab" -PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] ColorSchemes = "3" -CondaPkg = "0.2" -PythonPlot = "1.0" -Reexport = "1" +Requires = "1.3.0" +PythonPlot = "1" +PyPlot = "2" julia = "1" + +[extensions] +PyPlotSlimExt = "PyPlot" +PythonPlotSlimExt = "PythonPlot" + +[weakdeps] +PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" +PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index 5b25dfa..0000000 --- a/deps/build.jl +++ /dev/null @@ -1,14 +0,0 @@ -using PythonPlot - -deps = ("colorcet", "seiscm") - -function install_pkg(pkg) - if get(ENV, "JULIA_CONDAPKG_BACKEND", "conda") == "Null" - pyexe = PythonPlot.PythonCall.python_executable_path() - run(Cmd(`$(pyexe) -m pip install --user $(pkg)`)) - end -end - -for pkg in deps - install_pkg(pkg) -end \ No newline at end of file diff --git a/examples/plot_example.jl b/examples/plot_example.jl index d80402d..a4b78df 100644 --- a/examples/plot_example.jl +++ b/examples/plot_example.jl @@ -9,7 +9,7 @@ #' This example is converted to a markdown file for the documentation. #' # Import SlimPlotting, SegyIO to read seismic data, JLD2 for hdf5-like files -using SlimPlotting, SegyIO, JLD2 +using PythonPlot, SlimPlotting, SegyIO, JLD2 #' # Initialize all needed data @@ -54,26 +54,26 @@ shotp = shotrec([shot], 0.008, geometry([xloc])); #' # Model perturbation #' We plot here a model perturbation (i.e a Reverse-time Migrated image) and compare a few colormaps: -#' - The `seiscm.seimic` colormap -#' - The standard matplotlib `Greys` colormap #' - The perceptually accurate `Greys` colormap from colorcet +#' - The standard matplotlib `Greys` colormap +#' - Another perceptually accurate `Greys` colormap from colorcet figure(figsize = (10, 10)) subplot(311) -plot_simage(dmp; new_fig = false, name = "Seismic") +plot_simage(dmp; new_fig = false, name = "Colorcet Greys") subplot(312) plot_simage(dm, (10, 20); cmap = "Greys", new_fig = false, name = "Greys") subplot(313) -plot_simage(dm, (10, 20); cmap = :cet_CET_L1, new_fig = false, name = "Colorcet Greys") +plot_simage(dm, (10, 20); cmap = :cet_CET_L2, new_fig = false, name = "Colorcet Greys 2") tight_layout(); display(gcf()); #' # Velocity #' We plot here a velocity model and compare a few colormaps: -#' - The `seiscm.frequency` colormap -#' - The ColorSchemes `vik` colormap #' - The perceptually accurate `jet` colormap from colorcet named `cet_rainbow4` +#' - The ColorSchemes `vik` colormap +#' - The matplotlib `jet` figure(figsize = (10, 10)) subplot(311) @@ -81,16 +81,16 @@ plot_velocity(vpp; new_fig = false, name = "colorcet jet", cmap = "cet_rainbow4" subplot(312) plot_velocity(vp, (10, 20); cmap = :vik, new_fig = false, name = "ColorSchemes's vik") subplot(313) -plot_velocity(vp, (10, 20); cmap = seiscm(:frequency), new_fig = false, name = "Seiscm") +plot_velocity(vp, (10, 20); cmap = "jet", new_fig = false, name = "matplotlib jet") tight_layout(); display(gcf()); #' # Frequency slice #' We plot here a frequency slice for a seismic dataset and compare a few colormaps: -#' - The `seiscm.bwr` colormap -#' - The standard matplotlib `bwr` colormap #' - The perceptually accurate `bwr` colormap from colorcet named `cet_CET_D1A` +#' - The standard matplotlib `bwr` colormap +#' - The matplotlib "bwr # Frequency slice figure(figsize = (10, 5)) @@ -99,7 +99,7 @@ plot_fslice(fslice["Freq"][1, :, :], (12.5, 12.5); new_fig = false, name = "colo subplot(132) plot_fslice(fslicep; cmap = :bwr, new_fig = false, name = "bwr") subplot(133) -plot_fslice(fslicep; cmap = seiscm(:bwr), new_fig = false, name = "Seiscm bwr") +plot_fslice(fslicep; cmap = "bwr", new_fig = false, name = "Matplotlib bwr") tight_layout(); display(gcf()); @@ -109,9 +109,8 @@ display(gcf()); #' ## Seismic blue-white-red #' #' We plot here a frequency slice for a seismic dataset and compare a few colormaps for the `bwr` colormap: -#' - The `seiscm.bwr` colormap #' - The standard matplotlib `bwr` colormap -#' - The perceptually accurate `bwr` colormap from colorcet named `cet_CET_D1A` +#' - The perceptually accurate `bwr` colormap from colorcet named `cet_CET_D1A` and `cet_CET_D1` # Shot record figure(figsize = (10, 5)) @@ -120,7 +119,7 @@ plot_sdata(shotp; new_fig = false, name = "matplotlib seismic", cmap = "bwr") subplot(132) plot_sdata(shot, (12.5, 0.008); cmap = :cet_CET_D1A, new_fig = false, name = "Colorcet bwr") subplot(133) -plot_sdata(shot, (12.5, 0.008); cmap = seiscm(:bwr), new_fig = false, name = "Seismic bwr") +plot_sdata(shot, (12.5, 0.008); cmap = :cet_CET_D1, new_fig = false, name = "Colorcet bwr 2") tight_layout(); display(gcf()); diff --git a/ext/PyPlotSlimExt.jl b/ext/PyPlotSlimExt.jl new file mode 100644 index 0000000..2730dda --- /dev/null +++ b/ext/PyPlotSlimExt.jl @@ -0,0 +1,35 @@ +module PyPlotSlimExt + +import SlimPlotting: seiscm, getcmap + +isdefined(Base, :get_extension) ? (using PyPlot) : (using ..PyPlot) +isdefined(Base, :get_extension) ? (using PyPlot.PyCall) : (using ..PyPlot.PyCall) + +function tryimport(pkg::String) + pyi = try + PyPlot.pyimport(pkg) + catch e + if PyPlot.PyCall.conda + PyPlot.PyCall.Conda.pip_interop(true) + PyPlot.PyCall.Conda.pip("install", pkg) + else + run(PyPlot.PyCall.python_cmd(`-m pip install --user $(pkg)`)) + end + PyPlot.pyimport(pkg) + end + return pyi +end + +const cc = PyNULL() + +pypltref = PyPlot + +function __init__() + @info "Initializing PyPlotSlimExt" + # Import colorcet + copy!(cc, tryimport("colorcet")) +end + +getcmap(s) = ColorMap(s) + +end \ No newline at end of file diff --git a/ext/PythonPlotSlimExt.jl b/ext/PythonPlotSlimExt.jl new file mode 100644 index 0000000..3772310 --- /dev/null +++ b/ext/PythonPlotSlimExt.jl @@ -0,0 +1,34 @@ +module PythonPlotSlimExt + +import SlimPlotting: seiscm, getcmap +isdefined(Base, :get_extension) ? (using PythonPlot) : (using ..PythonPlot) + +function tryimport(pkg::String) + pyi = try + PythonPlot.pyimport(pkg) + catch e + if get(ENV, "JULIA_CONDAPKG_BACKEND", "conda") == "Null" + pyexe = PythonPlot.PythonCall.python_executable_path() + run(Cmd(`$(pyexe) -m pip install --user $(pkg)`)) + else + PythonPlot.CondaPkg.add_pip(pkg) + end + PythonPlot.pyimport(pkg) + end + return pyi +end + +const cc = PythonPlot.PythonCall.pynew() + +pypltref = PythonPlot + +function __init__() + @info "Initializing PythonPlotSlimExt" + # Import colorcet + PythonPlot.PythonCall.pycopy!(cc, tryimport("colorcet")) +end + +getcmap(s) = ColorMap(s) +getcmap(c::PythonPlot.PythonCall.Py) = c + +end diff --git a/src/SlimPlotting.jl b/src/SlimPlotting.jl index eb01099..d2a175b 100644 --- a/src/SlimPlotting.jl +++ b/src/SlimPlotting.jl @@ -1,21 +1,43 @@ +__precompile__() module SlimPlotting -using Statistics, ColorSchemes, Reexport -@reexport using PythonPlot +using Statistics, ColorSchemes -const cc = PythonPlot.PythonCall.pynew() -const scm = PythonPlot.PythonCall.pynew() +# Only needed if extension not available (julia < 1.9) +if !isdefined(Base, :get_extension) + using Requires +end function __init__() - ccall(:jl_generating_output, Cint, ()) == 1 && return nothing - # Import colorcet - PythonPlot.PythonCall.pycopy!(cc, PythonPlot.pyimport("colorcet")) - # Import SeisCM - PythonPlot.PythonCall.pycopy!(scm, PythonPlot.pyimport("seiscm")) + # Optional dependencies + @static if !isdefined(Base, :get_extension) + @require PyPlot="d330b81b-6aea-500a-939a-2ce795aea3ee" begin + @info "PyPlot compat enabled" + include("../ext/PyPlotSlimExt.jl") + end + + @require PythonPlot="274fc56d-3b97-40fa-a1cd-1b4a50311bf9" begin + @info "PythonPlot compat enabled" + include("../ext/PythonPlotSlimExt.jl") + end + end end export plot_fslice, plot_velocity, plot_simage, plot_sdata, wiggle_plot, compare_shots -export colorschemes, seiscm +export colorschemes + +function getcmap end + +function get_extension() + # get backend + ext = Base.get_extension(@__MODULE__, :PythonPlotSlimExt) + if isnothing(ext) + ext = Base.get_extension(@__MODULE__, :PyPlotSlimExt) + end + isnothing(ext) && throw(MissingException("No plotting backend found, either PyPlot or PythonPlot need to be loaded in the script")) + + return ext.pypltref +end # String conversion in case of legacy :symbol input for PyPlot to_string(x::Symbol) = string(x) @@ -23,15 +45,6 @@ to_string(x) = x to_symbol(x::String) = Symbol(x) to_symbol(x) = x - -""" - seiscm(name) - -Return the colormap `name` for seiscm. These colormap are preimported as a dictionnary -""" -seiscm(s::Symbol) = seiscm(to_string(s)) -seiscm(s::String) = PythonPlot.pygetattr(scm, s)() - """ _plot_with_units(image, spacing; perc=95, cmap=:cet_CET_L1, o=(0, 0), interp="hanning", aspect=nothing, d_scale=0, @@ -90,14 +103,18 @@ function _plot_with_units( # color map cmap = try - ColorMap(to_string(cmap)) + getcmap(to_string(cmap)) catch - ColorMap(colorschemes[to_symbol(cmap)].colors) + getcmap(colorschemes[to_symbol(cmap)].colors) end - new_fig && figure() + + backend = get_extension() + + # Create new figure + new_fig && backend.figure() # Plot if !isnothing(alpha) - imshow( + backend.imshow( scaled, vmin = ma, vmax = a, @@ -108,7 +125,7 @@ function _plot_with_units( alpha = alpha, ) else - imshow( + backend.imshow( scaled, vmin = ma, vmax = a, @@ -118,14 +135,14 @@ function _plot_with_units( extent = extent, ) end - xlabel("$(labels[1]) [$(units[1])]") - ylabel("$(labels[2]) [$(units[2])]") - title("$name") - cbar && colorbar(fraction = 0.046, pad = 0.04) + backend.xlabel("$(labels[1]) [$(units[1])]") + backend.ylabel("$(labels[2]) [$(units[2])]") + backend.title("$name") + cbar && backend.colorbar(fraction = 0.046, pad = 0.04) if ~isnothing(save) save == true ? filename = name : filename = save - savefig(filename, bbox_inches = "tight", dpi = 150) + backend.savefig(filename, bbox_inches = "tight", dpi = 150) end end @@ -165,7 +182,7 @@ function plot_simage(image; kw...) d = (1, 1) end kwd = Dict(kw) - cmap = pop!(kwd, :cmap, seiscm(:seismic)) + cmap = pop!(kwd, :cmap, :cet_CET_D1A) plot_simage(image.data, d; cmap = cmap, kwd...) end @@ -332,7 +349,7 @@ function compare_shots(image, image2, spacing; chunksize = 20, kw...) else cmap2 = pop!(kwd, :cmap, :cet_CET_D1A) if cmap2 == cmap1 - cmap2 = ColorMap(cmap1).reversed() + cmap2 = getcmap(cmap1).reversed() end end # Zero out to alternate @@ -449,19 +466,22 @@ function wiggle_plot( error("time_axis must be the same length as the number of rows in data") # Time gain tg = time_axis .^ t_scale - new_fig && figure() - ylim(maximum(time_axis), minimum(time_axis)) - xlim(minimum(xrec), maximum(xrec)) + backend = get_extension() + + new_fig && backend.figure() + + backend.ylim(maximum(time_axis), minimum(time_axis)) + backend.xlim(minimum(xrec), maximum(xrec)) for (i, xr) ∈ enumerate(xrec) x = tg .* data[:, i] x = dx[i] * x ./ maximum(x) .+ xr # rescale to avoid large spikes - plot(x, time_axis, "k-") - fill_betweenx(time_axis, xr, x, where = (x .> xr), color = "k") + backend.plot(x, time_axis, "k-") + backend.fill_betweenx(time_axis, xr, x, where = (x .> xr), color = "k") end - xlabel("X") - ylabel("Time") + backend.xlabel("X") + backend.ylabel("Time") end end # module