Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 519c1dc

Browse files
committed
DOC: add tutorial explaining imshow *origin* and *extent*
1 parent d9b5b4e commit 519c1dc

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""
2+
*origin* and *extent* in `~.Axes.imshow`
3+
========================================
4+
5+
:meth:`~.Axes.imshow` allows you to render an image (either a 2D array
6+
which will be color-mapped (based on *norm* and *cmap*) or and 3D RGB(A)
7+
array which will be used as-is) to a rectangular region in dataspace.
8+
The orientation of the image in the final rendering is controlled by
9+
the *origin* and *extent* kwargs (and attributes on the resulting
10+
`~.AxesImage` instance) and the data limits of the axes.
11+
12+
The *extent* kwarg controls the bounding box in data coordinates that
13+
the image will fill specified as ``(left, right, bottom, top)`` in
14+
**data coordinates**, the *origin* kwarg controls how the image fills
15+
that bounding box, and the orientation in the final rendered image is
16+
also affected by the axes limits.
17+
"""
18+
import numpy as np
19+
import matplotlib.pyplot as plt
20+
from matplotlib.gridspec import GridSpec
21+
22+
23+
def generate_imshow_demo_grid(auto_limits, extents):
24+
N = len(extents)
25+
fig = plt.figure(tight_layout=True)
26+
fig.set_size_inches(6, N * (11.25) / 5)
27+
gs = GridSpec(N, 5, figure=fig)
28+
29+
columns = {'label': [fig.add_subplot(gs[j, 0]) for j in range(N)],
30+
'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)],
31+
'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]}
32+
33+
d = np.arange(42).reshape(6, 7)
34+
35+
for origin in ['upper', 'lower']:
36+
for ax, extent in zip(columns[origin], extents):
37+
38+
im = ax.imshow(d, origin=origin, extent=extent)
39+
left, right, bottom, top = im.get_extent()
40+
arrow_style = {'arrowprops': {'arrowstyle': '-|>',
41+
'shrinkA': 0,
42+
'color': '0.5',
43+
'linewidth': 3}}
44+
ax.annotate('',
45+
(left, bottom + 2*np.sign(top - bottom)),
46+
(left, bottom),
47+
**arrow_style)
48+
ax.annotate('',
49+
(left + 2*np.sign(right - left), bottom),
50+
(left, bottom),
51+
**arrow_style)
52+
53+
if auto_limits or top > bottom:
54+
upper_string, lower_string = 'top', 'bottom'
55+
else:
56+
upper_string, lower_string = 'bottom', 'top'
57+
58+
if auto_limits or left < right:
59+
port_string, starboard_string = 'left', 'right'
60+
else:
61+
port_string, starboard_string = 'right', 'left'
62+
63+
bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"}
64+
ann_kwargs = {'xycoords': 'axes fraction',
65+
'textcoords': 'offset points',
66+
'bbox': bbox_kwargs}
67+
68+
ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1),
69+
ha='center', va='top', **ann_kwargs)
70+
ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1),
71+
ha='center', va='bottom', **ann_kwargs)
72+
73+
ax.annotate(port_string, xy=(0, .5), xytext=(1, 0),
74+
ha='left', va='center', rotation=90,
75+
**ann_kwargs)
76+
ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0),
77+
ha='right', va='center', rotation=-90,
78+
**ann_kwargs)
79+
80+
ax.set_title(f'origin: {origin}')
81+
82+
if not auto_limits:
83+
ax.set_xlim(-1, 7)
84+
ax.set_ylim(-1, 6)
85+
86+
for ax, extent in zip(columns['label'], extents):
87+
text_kwargs = {'ha': 'right',
88+
'va': 'center',
89+
'xycoords': 'axes fraction',
90+
'xy': (1, .5)}
91+
if extent is None:
92+
ax.annotate('None', **text_kwargs)
93+
ax.set_title('`extent=`')
94+
else:
95+
left, right, bottom, top = extent
96+
text = ('left: {left:0.1f}\nright: {right:0.1f}\n' +
97+
'bottom: {bottom:0.1f}\ntop: {top:0.1f}\n').format(
98+
left=left, right=right, bottom=bottom, top=top)
99+
100+
ax.annotate(text, **text_kwargs)
101+
ax.axis('off')
102+
103+
104+
extents = (None,
105+
(-0.5, 6.5, -0.5, 5.5),
106+
(-0.5, 6.5, 5.5, -0.5),
107+
(6.5, -0.5, -0.5, 5.5),
108+
(6.5, -0.5, 5.5, -0.5))
109+
110+
###############################################################################
111+
#
112+
#
113+
# First, using *extent* we pick a bounding box in dataspace that the
114+
# image will fill and then interpolate/resample the underlying data to
115+
# fill that box.
116+
#
117+
# - If ``origin='lower'`` than the ``[0, 0]`` entry is closest to the
118+
# ``(left, bottom)`` corner of the bounding box and moving closer to
119+
# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to
120+
# higher indexed rows and moving towards ``(right, bottom)`` moves you
121+
# along the ``[0, :]`` axis of the array to higher indexed columns
122+
#
123+
# - If ``origin='upper'`` then the ``[-1, 0]`` entry is closest to the
124+
# ``(left, bottom)`` corner of the bounding box and moving towards
125+
# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to
126+
# lower index rows and moving towards ``(right, bottom)`` moves you
127+
# along the ``[-1, :]`` axis of the array to higher indexed columns
128+
#
129+
# To demonstrate this we will plot a linear ramp
130+
# ``np.arange(42).reshape(6, 7)`` with varying parameters.
131+
#
132+
133+
generate_imshow_demo_grid(True, extents[:1])
134+
135+
###############################################################################
136+
#
137+
# If we only specify *origin* we can see why it is so named. For
138+
# ``origin='upper'`` the ``[0, 0]`` pixel is on the upper left and for
139+
# ``origin='lower'`` the ``[0, 0]`` pixel is in the lower left [#]_.
140+
# The gray arrows are attached to the ``(left, bottom)`` corner of the
141+
# image. There are two tricky things going on here: first the default
142+
# value of *extent* depends on the value of *origin* and second the x
143+
# and y limits are adjusted to match the extent. The default *extent*
144+
# is ``(-0.5, numcols-0.5, numrows-0.5, -0.5)`` when ``origin ==
145+
# 'upper'`` and ``(-0.5, numcols-0.5, -0.5, numrows-0.5)`` when ``origin
146+
# == 'lower'`` which puts the pixel centers on integer positions and the
147+
# ``[0, 0]`` pixel at ``(0, 0)`` in dataspace.
148+
#
149+
#
150+
# .. [#] The default value of *origin* is set by :rc:`image.origin`
151+
# which defaults to ``'upper'`` to match the matrix indexing
152+
# conventions in math and computer graphics image indexing
153+
# conventions.
154+
155+
generate_imshow_demo_grid(True, extents[1:])
156+
157+
###############################################################################
158+
#
159+
# If the axes is set to autoscale, then view limits of the axes are set
160+
# to match the *extent* which ensures that the coordinate set by
161+
# ``(left, bottom)`` is at the bottom left of the axes! However, this
162+
# may invert the axis so they do not increase in the 'natural' direction.
163+
#
164+
165+
generate_imshow_demo_grid(False, extents)
166+
167+
###############################################################################
168+
#
169+
# If we fix the axes limits so ``(0, 0)`` is at the bottom left and
170+
# increases to up and to the right (from the viewer point of view) then
171+
# we can see that:
172+
#
173+
# - The ``(left, bottom)`` anchors the image which then fills the
174+
# box going towards the ``(right, top)`` point in data space.
175+
# - The first column is always closest to the 'left'.
176+
# - *origin* controls if the first row is closest to 'top' or 'bottom'.
177+
# - The image may be inverted along either direction.
178+
# - The 'left-right' and 'top-bottom' sense of the image is uncoupled from
179+
# the orientation on the screen.

0 commit comments

Comments
 (0)