GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
732 stars 214 forks source link

pygmt.config: pen settings are not reverted correctly when set in a context manager #3252

Open seisman opened 2 months ago

seisman commented 2 months ago

Haven't found time to see why it doesn't work.

Here is the minimal example:

import pygmt

fig = pygmt.Figure()

fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True)

fig.shift_origin(xshift="w+1")
with pygmt.config(MAP_FRAME_PEN="4p,blue"):
    fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True)

fig.shift_origin(xshift="w+1")
fig.basemap(region=[0, 10, 0, 10], projection="X10c", frame=True)
fig.show()

The 3rd basemap is expected to have the default frame pen setting. Now the pen color is correct, but the pen thickness is incorrect:

Actual Expected
map map

The expected image is produced by:

gmt begin map
    gmt basemap -R0/10/0/10 -JX10c -Baf
    gmt basemap -R0/10/0/10 -JX10c -Baf -Xw+1c --MAP_FRAME_PEN=3p,blue
    gmt basemap -R0/10/0/10 -JX10c -Baf -Xw+1c
gmt end show
seisman commented 2 months ago

Currently, pygmt.config is implemented (in PR #293) like a context manager. When entering the context manager, we first get the current values of GMT configurations, store them in a dictionary, and then call gmt set to set the GMT configurations to new values. When exiting the context manager, we then restore the old values of GMT configurations by using gmt set again.

THe current values of GMT configurations are obtained by calling the GMT API function GMT_Get_Default (or the Session.get_default wrapper), but this function doesn't work as we expected.

The following test explains what's happening:

In [1]: from pygmt.clib import Session

# Get the current value
In [2]: with Session() as lib:
   ...:     print(lib.get_default("MAP_FRAME_PEN"))
   ...:
black   

# Set to a new value
In [3]: with Session() as lib:
   ...:     lib.call_module("set", "MAP_FRAME_PEN=4p,blue")
   ...:

# Get the current value
In [4]: with Session() as lib:
   ...:     print(lib.get_default("MAP_FRAME_PEN"))
   ...:
4p,blue

# Restore to the old value
In [5]: with Session() as lib:
   ...:     lib.call_module("set", "MAP_FRAME_PEN=black")
   ...:

# Check the current value
In [6]: with Session() as lib:
   ...:     print(lib.get_default("MAP_FRAME_PEN"))
   ...:
4p,black

The main issue is that Session.get_default("MAP_FRAME_PEN") returns black, but we expect a string like thicker,black. The string is returned from the gmt_putpen function (https://github.com/GenericMappingTools/gmt/blob/master/src/gmt_support.c#L7555). I think this is the desired behavior of the function, and it's not an upstream bug.

seisman commented 2 months ago

Here is the output from gmt defaults in modern mode. It seems that, all PEN related configurations have the value black, rather than something like default,black. So, I expect all other PEN-related configurations have the same issue.

>>> from pygmt.clib import Session
>>> with Session() as lib:
...    lib.call_module("defaults", "")
#
# GMT 6.5.0 Defaults file
#
# COLOR Parameters
#
COLOR_BACKGROUND               = black
COLOR_FOREGROUND               = white
COLOR_CPT                      = turbo
COLOR_NAN                      = 128
COLOR_MODEL                    = none
COLOR_HSV_MIN_S                = 1
COLOR_HSV_MAX_S                = 0.1
COLOR_HSV_MIN_V                = 0.3
COLOR_HSV_MAX_V                = 1
COLOR_SET                      = #0072BD,#D95319,#EDB120,#7E2F8E,#77AC30,#4DBEEE,#A2142F
#
# DIR Parameters
#
DIR_CACHE                      = /home/seisman/.gmt/cache
DIR_DATA                       = 
DIR_DCW                        = /home/seisman/opt/miniforge/envs/pygmt/share/dcw-gmt
DIR_GSHHG                      = /home/seisman/opt/miniforge/envs/pygmt/share/gshhg-gmt
#
# FONT Parameters
#
FONT_ANNOT_PRIMARY             = auto,Helvetica,black
FONT_ANNOT_SECONDARY           = auto,Helvetica,black
FONT_HEADING                   = auto,Helvetica-Bold,black
FONT_LABEL                     = auto,Helvetica,black
FONT_LOGO                      = 8p,Helvetica,black
FONT_SUBTITLE                  = auto,Helvetica-Bold,black
FONT_TAG                       = auto,Helvetica,black
FONT_TITLE                     = auto,Helvetica-Bold,black
#
# FORMAT Parameters
#
FORMAT_CLOCK_IN                = hh:mm:ss
FORMAT_CLOCK_OUT               = hh:mm:ss
FORMAT_CLOCK_MAP               = hh:mm:ss
FORMAT_DATE_IN                 = yyyy-mm-dd
FORMAT_DATE_OUT                = yyyy-mm-dd
FORMAT_DATE_MAP                = yyyy-mm-dd
FORMAT_GEO_OUT                 = D
FORMAT_GEO_MAP                 = ddd:mm:ssF
FORMAT_FLOAT_OUT               = %.12g
FORMAT_FLOAT_MAP               = %.12g
FORMAT_TIME_PRIMARY_MAP        = full
FORMAT_TIME_SECONDARY_MAP      = full
FORMAT_TIME_STAMP              = %Y %b %d %H:%M:%S
#
# GMT Miscellaneous Parameters
#
GMT_DATA_SERVER                = oceania
GMT_DATA_SERVER_LIMIT          = unlimited
GMT_DATA_UPDATE_INTERVAL       = 1d
GMT_COMPATIBILITY              = 6
GMT_CUSTOM_LIBS                = 
GMT_EXPORT_TYPE                = double
GMT_EXTRAPOLATE_VAL            = NaN
GMT_FFT                        = auto
GMT_GRAPHICS_DPU               = 300i
GMT_GRAPHICS_FORMAT            = pdf
GMT_HISTORY                    = true
GMT_INTERPOLANT                = akima
GMT_LANGUAGE                   = us
GMT_MAX_CORES                  = 0
GMT_THEME                      = modern
GMT_TRIANGULATE                = Shewchuk
GMT_VERBOSE                    = warning
#
# I/O Parameters
#
IO_COL_SEPARATOR               = tab
IO_FIRST_HEADER                = maybe
IO_GRIDFILE_FORMAT             = nf
IO_GRIDFILE_SHORTHAND          = false
IO_HEADER                      = false
IO_HEADER_MARKER               = #%!;"',#
IO_N_HEADER_RECS               = 0
IO_NAN_RECORDS                 = pass
IO_NC4_CHUNK_SIZE              = auto
IO_NC4_DEFLATION_LEVEL         = 3
IO_LONLAT_TOGGLE               = false
IO_SEGMENT_BINARY              = 2
IO_SEGMENT_MARKER              = >
#
# MAP Parameters
#
MAP_ANNOT_MIN_ANGLE            = 20
MAP_ANNOT_MIN_SPACING          = auto
MAP_ANNOT_OBLIQUE              = separate,lon_horizontal,lat_horizontal,tick_extend
MAP_ANNOT_OFFSET_PRIMARY       = auto
MAP_ANNOT_OFFSET_SECONDARY     = auto
MAP_ANNOT_ORTHO                = we
MAP_DEFAULT_PEN                = default,black
MAP_DEGREE_SYMBOL              = degree
MAP_EMBELLISHMENT_MODE         = auto
MAP_FRAME_AXES                 = auto
MAP_FRAME_PEN                  = black
MAP_FRAME_PERCENT              = 100
MAP_FRAME_TYPE                 = fancy
MAP_FRAME_WIDTH                = auto
MAP_GRID_CROSS_SIZE_PRIMARY    = 0p
MAP_GRID_CROSS_SIZE_SECONDARY  = 0p
MAP_GRID_PEN_PRIMARY           = black
MAP_GRID_PEN_SECONDARY         = black
MAP_HEADING_OFFSET             = auto
MAP_LABEL_MODE                 = annot
MAP_LABEL_OFFSET               = auto
MAP_LINE_STEP                  = 0.75p
MAP_LOGO                       = false
MAP_LOGO_POS                   = BL/-54p/-54p
MAP_ORIGIN_X                   = 72p
MAP_ORIGIN_Y                   = 72p
MAP_POLAR_CAP                  = auto
MAP_SCALE_HEIGHT               = 5p
MAP_SYMBOL_PEN_SCALE           = 15%
MAP_TICK_LENGTH_PRIMARY        = auto
MAP_TICK_LENGTH_SECONDARY      = auto
MAP_TICK_PEN_PRIMARY           = black
MAP_TICK_PEN_SECONDARY         = black
MAP_TITLE_OFFSET               = auto
MAP_VECTOR_SHAPE               = 0.5
#
# Projection Parameters
#
PROJ_AUX_LATITUDE              = authalic
PROJ_DATUM                     = 
PROJ_ELLIPSOID                 = WGS-84
PROJ_GEODESIC                  = Vincenty
PROJ_LENGTH_UNIT               = cm
PROJ_MEAN_RADIUS               = authalic
PROJ_SCALE_FACTOR              = default
#
# PostScript Parameters
#
PS_CHAR_ENCODING               = ISOLatin1+
PS_COLOR_MODEL                 = rgb
PS_COMMENTS                    = false
PS_CONVERT                     = A
PS_IMAGE_COMPRESS              = deflate,5
PS_LINE_CAP                    = butt
PS_LINE_JOIN                   = miter
PS_MITER_LIMIT                 = 35
PS_MEDIA                       = 32767x32767
PS_PAGE_COLOR                  = white
PS_PAGE_ORIENTATION            = portrait
PS_SCALE_X                     = 1
PS_SCALE_Y                     = 1
PS_TRANSPARENCY                = Normal
#
# Calendar/Time Parameters
#
TIME_EPOCH                     = 1970-01-01T00:00:00
TIME_IS_INTERVAL               = off
TIME_INTERVAL_FRACTION         = 0.5
TIME_LEAP_SECONDS              = false
TIME_REPORT                    = none
TIME_UNIT                      = s
TIME_WEEK_START                = Monday
TIME_Y2K_OFFSET_YEAR           = 1950

Calling gmt get directly also doesn't work:

>>> with Session() as lib:
...    lib.call_module("get", "MAP_FRAME_PEN")
black
yvonnefroehlich commented 2 months ago

Here is the output from gmt defaults in modern mode. It seems that, all PEN related configurations have the value black, rather than something like default,black. So, I expect all other PEN-related configurations have the same issue.

Yes, the same issue occurs with MAP_GRID_PEN and MAP_TICK_PEN. Also, reverting the line style does not work.

import pygmt

size = 5
region = [-size, size, -size, size]
projection = "X3c"
frame = "afg"

fig = pygmt.Figure()

# Left
fig.basemap(region=region, projection=projection, frame=frame)

# Middle
fig.shift_origin(xshift="w+1")
with pygmt.config(
        MAP_FRAME_PEN="darkred,dashed",
        MAP_GRID_PEN="blue,dashed",
        MAP_TICK_PEN="2p,magenta",
):
    fig.basemap(region=region, projection=projection, frame=frame)

# Right
fig.shift_origin(xshift="w+1")
fig.basemap(region=region, projection=projection, frame=frame)

fig.show()

config_local_revert_pen