Skip to content

Commit 9e80938

Browse files
committed
Figure.image: Add parameters position/width/height/dpi/replicate to specify image position and properties
1 parent 3dd7379 commit 9e80938

File tree

5 files changed

+124
-20
lines changed

5 files changed

+124
-20
lines changed

examples/gallery/images/image.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@
66
many formats (e.g., png, jpg, eps, pdf) on a figure. We must specify the filename via
77
the ``imagefile`` parameter or simply use the filename as the first argument. You can
88
also use a full URL pointing to your desired image. The ``position`` parameter allows
9-
us to set a reference point on the map for the image.
9+
us to place the image at a specific location on the plot.
1010
"""
1111

1212
# %%
1313
from pathlib import Path
1414

1515
import pygmt
16+
from pygmt.params import Position
1617

1718
fig = pygmt.Figure()
1819
fig.basemap(region=[0, 2, 0, 2], projection="X10c", frame=True)
1920

20-
# Place and center ("+jCM") the image "needle.jpg" provided by GMT to the position
21-
# ("+g") 1/1 on the current plot, scale it to a width of 8 centimeters ("+w") and draw
22-
# a rectangular border around it
21+
# Place the center of the image "needle.jpg" provided by GMT to the position (1, 1) on
22+
# the current plot, scale it to a width of 8 centimeters and draw a rectangular border
23+
# around it.
2324
fig.image(
2425
imagefile="https://oceania.generic-mapping-tools.org/cache/needle.jpg",
25-
position="g1/1+w8c+jCM",
26+
position=Position((1, 1), cstype="mapcoords", anchor="MC"),
27+
width="8c",
2628
box=True,
2729
)
2830

pygmt/src/image.py

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@
55
from collections.abc import Sequence
66
from typing import Literal
77

8-
from pygmt._typing import PathLike
8+
from pygmt._typing import AnchorCode, PathLike
99
from pygmt.alias import Alias, AliasSystem
1010
from pygmt.clib import Session
1111
from pygmt.helpers import build_arg_list, fmt_docstring, use_alias
12-
from pygmt.params import Box
12+
from pygmt.params import Box, Position
13+
from pygmt.src._common import _parse_position
1314

1415

1516
@fmt_docstring
16-
@use_alias(D="position", G="bitcolor")
17+
@use_alias(G="bitcolor")
1718
def image( # noqa: PLR0913
1819
self,
1920
imagefile: PathLike,
21+
position: Position | Sequence[float | str] | AnchorCode | None = None,
22+
width: float | str | None = None,
23+
height: float | str | None = None,
24+
dpi: float | str | None = None,
25+
replicate: int | Sequence[int] | None = None,
2026
box: Box | bool = False,
2127
monochrome: bool = False,
2228
invert: bool = False,
@@ -33,10 +39,10 @@ def image( # noqa: PLR0913
3339
r"""
3440
Plot raster or EPS images.
3541
36-
Reads Encapsulated PostScript (EPS) or raster image files and plots them. The
37-
image can be scaled arbitrarily, and 1-bit raster images can be:
42+
Reads an Encapsulated PostScript file or a raster image file and plot it on a map.
43+
The image can be scaled arbitrarily, and 1-bit raster images can be:
3844
39-
- inverted, i.e., black pixels (on) becomes white (off) and vice versa.
45+
- inverted, i.e., black pixels (on) become white (off) and vice versa.
4046
- colorized, by assigning different foreground and background colors.
4147
- made transparent where either the back- or foreground is painted.
4248
@@ -50,6 +56,7 @@ def image( # noqa: PLR0913
5056
5157
$aliases
5258
- B = frame
59+
- D = position, **+w**: width/height, **+r**: dpi, **+n**: replicate
5360
- F = box
5461
- I = invert
5562
- J = projection
@@ -66,11 +73,34 @@ def image( # noqa: PLR0913
6673
An Encapsulated PostScript (EPS) file or a raster image file. An EPS file must
6774
contain an appropriate BoundingBox. A raster file can have a depth of 1, 8, 24,
6875
or 32 bits and is read via GDAL.
69-
position : str
70-
[**g**\|\ **j**\|\ **J**\|\ **n**\|\ **x**]\ *refpoint*\ **+r**\ *dpi*\
71-
**+w**\ [**-**]\ *width*\ [/*height*]\ [**+j**\ *justify*]\
72-
[**+n**\ *nx*\ [/*ny*]]\ [**+o**\ *dx*\ [/*dy*]].
73-
Set reference point on the map for the image.
76+
position
77+
Position of the GMT logo on the plot. It can be specified in multiple ways:
78+
79+
- A :class:`pygmt.params.Position` object to fully control the reference point,
80+
anchor point, and offset.
81+
- A sequence of two values representing the x and y coordinates in plot
82+
coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``.
83+
- A :doc:`2-character justification code </techref/justification_codes>` for a
84+
position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot.
85+
86+
If not specified, defaults to the bottom-left corner of the plot (position
87+
``(0, 0)`` with anchor ``"BL"``).
88+
width
89+
height
90+
Width (and height) of the image in plot coordinates (inches, cm, etc.). If
91+
``height`` (or ``width``) is set to 0, then the original aspect ratio of the
92+
image is maintained. If ``width`` (or ``height``) is negative, the absolute
93+
value is used to interpolate image to the device resolution using the PostScript
94+
image operator. If neither dimensions nor ``dpi`` are set then revert to the
95+
default dpi [:gmt-term:`GMT_GRAPHICS_DPU`].
96+
dpi
97+
Set the dpi of the image in dots per inch, or append **c** to indicate this is
98+
dots per cm.
99+
replicate
100+
*nx* or (*nx*, *ny*).
101+
Replicate the (scaled) image *nx* times in the horizontal direction, and *ny*
102+
times in the vertical direction. If a single integer *nx* is given, *ny* = *nx*.
103+
[Default is (1, 1)].
74104
box
75105
Draw a background box behind the image. If set to ``True``, a simple rectangular
76106
box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance,
@@ -104,7 +134,24 @@ def image( # noqa: PLR0913
104134
"""
105135
self._activate_figure()
106136

137+
position = _parse_position(
138+
position,
139+
kwdict={"width": width, "height": height, "dpi": dpi, "replicate": replicate},
140+
default=Position((0, 0), cstype="plotcoords"), # Default to (0,0) in plotcoords
141+
)
142+
143+
# width is required when only height is given.
144+
if width is None and height is not None:
145+
width = 0
146+
107147
aliasdict = AliasSystem(
148+
D=[
149+
Alias(position, name="position"),
150+
Alias(width, name="width", prefix="+w"), # +wwidth/height
151+
Alias(height, name="height", prefix="/"),
152+
Alias(replicate, name="replicate", prefix="+n", sep="/", size=2),
153+
Alias(dpi, name="dpi", prefix="+r"),
154+
],
108155
F=Alias(box, name="box"),
109156
M=Alias(monochrome, name="monochrome"),
110157
I=Alias(invert, name="invert"),
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
outs:
2-
- md5: d7d0d71a44a232d5907dbd44f7a08f18
3-
size: 30811
2+
- md5: 3bafd31eb0374ec175c1283c95ab0530
3+
size: 24065
44
path: test_image.png
5+
hash: md5
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: b2984015b085a78cda9c90fbd500b97e
3+
size: 64696
4+
hash: md5
5+
path: test_image_complete.png

pygmt/tests/test_image.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import pytest
66
from pygmt import Figure
7-
from pygmt.params import Box
7+
from pygmt.exceptions import GMTInvalidInput
8+
from pygmt.params import Box, Position
89

910

1011
@pytest.mark.mpl_image_compare
@@ -13,5 +14,53 @@ def test_image():
1314
Place images on map.
1415
"""
1516
fig = Figure()
16-
fig.image(imagefile="@circuit.png", position="x0/0+w2c", box=Box(pen="thin,blue"))
17+
fig.image(imagefile="@circuit.png")
1718
return fig
19+
20+
21+
@pytest.mark.mpl_image_compare
22+
def test_image_complete():
23+
"""
24+
Test all parameters of image.
25+
"""
26+
fig = Figure()
27+
fig.image(
28+
imagefile="@circuit.png",
29+
position=Position((0, 0)),
30+
width="4c",
31+
height="0",
32+
replicate=(2, 1),
33+
dpi="300",
34+
box=Box(pen="thin,blue"),
35+
)
36+
return fig
37+
38+
39+
@pytest.mark.mpl_image_compare(filename="test_image_complete.png")
40+
def test_image_position_deprecated_syntax():
41+
"""
42+
Test that passing the deprecated GMT CLI syntax string to 'position' works.
43+
"""
44+
fig = Figure()
45+
fig.image(
46+
imagefile="@circuit.png",
47+
position="x0/0+w4c/0c+n2/1+r300",
48+
box=Box(pen="thin,blue"),
49+
)
50+
return fig
51+
52+
53+
def test_image_position_mixed_syntax():
54+
"""
55+
Test that an error is raised when 'position' is given as a raw GMT CLI string
56+
and conflicts with other parameters.
57+
"""
58+
fig = Figure()
59+
with pytest.raises(GMTInvalidInput):
60+
fig.image(imagefile="@circuit.png", position="x0/0", width="4c")
61+
with pytest.raises(GMTInvalidInput):
62+
fig.image(imagefile="@circuit.png", position="x0/0", height="3c")
63+
with pytest.raises(GMTInvalidInput):
64+
fig.image(imagefile="@circuit.png", position="x0/0", dpi="300")
65+
with pytest.raises(GMTInvalidInput):
66+
fig.image(imagefile="@circuit.png", position="x0/0", replicate=(2, 1))

0 commit comments

Comments
 (0)