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

Skip to content

Commit e22a16a

Browse files
authored
Merge pull request #10947 from tacaswell/doc_imshow_extent
DOC: add tutorial explaining imshow *origin* and *extent*
2 parents bdaafd9 + 206c3ca commit e22a16a

File tree

2 files changed

+266
-1
lines changed

2 files changed

+266
-1
lines changed

doc/_static/mpl.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ div.deprecated span.versionmodified {
416416
font-weight: bold;
417417
}
418418

419-
div.green {
419+
div.green, div.hint {
420420
color: #468847;
421421
background-color: #dff0d8;
422422
border: 1px solid #d6e9c6;
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
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+
.. hint:: Most of the code below is used for adding labels and informative
19+
text to the plots. The described effects of *origin* and *extent* can be
20+
seen in the plots without the need to follow all code details.
21+
22+
For a quick understanding, you may want to skip the code details below and
23+
directly continue with the discussion of the results.
24+
"""
25+
import numpy as np
26+
import matplotlib.pyplot as plt
27+
from matplotlib.gridspec import GridSpec
28+
29+
30+
def index_to_coordinate(index, extent, origin):
31+
"""Return the pixel center of an index."""
32+
left, right, bottom, top = extent
33+
34+
hshift = 0.5 * np.sign(right - left)
35+
left, right = left + hshift, right - hshift
36+
vshift = 0.5 * np.sign(top - bottom)
37+
bottom, top = bottom + vshift, top - vshift
38+
39+
if origin == 'upper':
40+
bottom, top = top, bottom
41+
42+
return {
43+
"[0, 0]": (left, bottom),
44+
"[M', 0]": (left, top),
45+
"[0, N']": (right, bottom),
46+
"[M', N']": (right, top),
47+
}[index]
48+
49+
50+
def get_index_label_pos(index, extent, origin, inverted_xindex):
51+
"""
52+
Return the desired position and horizontal alignment of an index label.
53+
"""
54+
if extent is None:
55+
extent = lookup_extent(origin)
56+
left, right, bottom, top = extent
57+
x, y = index_to_coordinate(index, extent, origin)
58+
59+
is_x0 = index[-2:] == "0]"
60+
halign = 'left' if is_x0 ^ inverted_xindex else 'right'
61+
hshift = 0.5 * np.sign(left - right)
62+
x += hshift * (1 if is_x0 else -1)
63+
return x, y, halign
64+
65+
66+
def get_color(index, data, cmap):
67+
"""Return the data color of an index."""
68+
val = {
69+
"[0, 0]": data[0, 0],
70+
"[0, N']": data[0, -1],
71+
"[M', 0]": data[-1, 0],
72+
"[M', N']": data[-1, -1],
73+
}[index]
74+
return cmap(val / data.max())
75+
76+
77+
def lookup_extent(origin):
78+
"""Return extent for label positioning when not given explicitly."""
79+
if origin == 'lower':
80+
return (-0.5, 6.5, -0.5, 5.5)
81+
else:
82+
return (-0.5, 6.5, 5.5, -0.5)
83+
84+
85+
def set_extent_None_text(ax):
86+
ax.text(3, 2.5, 'equals\nextent=None', size='large',
87+
ha='center', va='center', color='w')
88+
89+
90+
def plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim):
91+
"""Actually run ``imshow()`` and add extent and index labels."""
92+
im = ax.imshow(data, origin=origin, extent=extent)
93+
94+
# extent labels (left, right, bottom, top)
95+
left, right, bottom, top = im.get_extent()
96+
if xlim is None or top > bottom:
97+
upper_string, lower_string = 'top', 'bottom'
98+
else:
99+
upper_string, lower_string = 'bottom', 'top'
100+
if ylim is None or left < right:
101+
port_string, starboard_string = 'left', 'right'
102+
inverted_xindex = False
103+
else:
104+
port_string, starboard_string = 'right', 'left'
105+
inverted_xindex = True
106+
bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"}
107+
ann_kwargs = {'xycoords': 'axes fraction',
108+
'textcoords': 'offset points',
109+
'bbox': bbox_kwargs}
110+
ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1),
111+
ha='center', va='top', **ann_kwargs)
112+
ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1),
113+
ha='center', va='bottom', **ann_kwargs)
114+
ax.annotate(port_string, xy=(0, .5), xytext=(1, 0),
115+
ha='left', va='center', rotation=90,
116+
**ann_kwargs)
117+
ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0),
118+
ha='right', va='center', rotation=-90,
119+
**ann_kwargs)
120+
ax.set_title('origin: {origin}'.format(origin=origin))
121+
122+
# index labels
123+
for index in ["[0, 0]", "[0, N']", "[M', 0]", "[M', N']"]:
124+
tx, ty, halign = get_index_label_pos(index, extent, origin,
125+
inverted_xindex)
126+
facecolor = get_color(index, data, im.get_cmap())
127+
ax.text(tx, ty, index, color='white', ha=halign, va='center',
128+
bbox={'boxstyle': 'square', 'facecolor': facecolor})
129+
if xlim:
130+
ax.set_xlim(*xlim)
131+
if ylim:
132+
ax.set_ylim(*ylim)
133+
134+
135+
def generate_imshow_demo_grid(extents, xlim=None, ylim=None):
136+
N = len(extents)
137+
fig = plt.figure(tight_layout=True)
138+
fig.set_size_inches(6, N * (11.25) / 5)
139+
gs = GridSpec(N, 5, figure=fig)
140+
141+
columns = {'label': [fig.add_subplot(gs[j, 0]) for j in range(N)],
142+
'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)],
143+
'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]}
144+
x, y = np.ogrid[0:6, 0:7]
145+
data = x + y
146+
147+
for origin in ['upper', 'lower']:
148+
for ax, extent in zip(columns[origin], extents):
149+
plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim)
150+
151+
for ax, extent in zip(columns['label'], extents):
152+
text_kwargs = {'ha': 'right',
153+
'va': 'center',
154+
'xycoords': 'axes fraction',
155+
'xy': (1, .5)}
156+
if extent is None:
157+
ax.annotate('None', **text_kwargs)
158+
ax.set_title('extent=')
159+
else:
160+
left, right, bottom, top = extent
161+
text = ('left: {left:0.1f}\nright: {right:0.1f}\n' +
162+
'bottom: {bottom:0.1f}\ntop: {top:0.1f}\n').format(
163+
left=left, right=right, bottom=bottom, top=top)
164+
165+
ax.annotate(text, **text_kwargs)
166+
ax.axis('off')
167+
return columns
168+
169+
170+
###############################################################################
171+
#
172+
# Default extent
173+
# --------------
174+
#
175+
# First, let's have a look at the default `extent=None`
176+
177+
generate_imshow_demo_grid(extents=[None])
178+
179+
###############################################################################
180+
#
181+
# Generally, for an array of shape (M, N), the first index runs along the
182+
# vertical, the second index runs along the horizontal.
183+
# The pixel centers are at integer positions ranging from 0 to ``N' = N - 1``
184+
# horizontally and from 0 to ``M' = M - 1`` vertically.
185+
# *origin* determines how to the data is filled in the bounding box.
186+
#
187+
# For ``origin='lower'``:
188+
#
189+
# - [0, 0] is at (left, bottom)
190+
# - [M', 0] is at (left, top)
191+
# - [0, N'] is at (right, bottom)
192+
# - [M', N'] is at (right, top)
193+
#
194+
# ``origin='upper'`` reverses the vertical axes direction and filling:
195+
#
196+
# - [0, 0] is at (left, top)
197+
# - [M', 0] is at (left, bottom)
198+
# - [0, N'] is at (right, top)
199+
# - [M', N'] is at (right, bottom)
200+
#
201+
# In summary, the position of the [0, 0] index as well as the extent are
202+
# influenced by *origin*:
203+
#
204+
# ====== =============== ==========================================
205+
# origin [0, 0] position extent
206+
# ====== =============== ==========================================
207+
# upper top left ``(-0.5, numcols-0.5, numrows-0.5, -0.5)``
208+
# lower bottom left ``(-0.5, numcols-0.5, -0.5, numrows-0.5)``
209+
# ====== =============== ==========================================
210+
#
211+
# The default value of *origin* is set by :rc:`image.origin` which defaults
212+
# to ``'upper'`` to match the matrix indexing conventions in math and
213+
# computer graphics image indexing conventions.
214+
#
215+
#
216+
# Explicit extent
217+
# ---------------
218+
#
219+
# By setting *extent* we define the coordinates of the image area. The
220+
# underlying image data is interpolated/resampled to fill that area.
221+
#
222+
# If the axes is set to autoscale, then the view limits of the axes are set
223+
# to match the *extent* which ensures that the coordinate set by
224+
# ``(left, bottom)`` is at the bottom left of the axes! However, this
225+
# may invert the axis so they do not increase in the 'natural' direction.
226+
#
227+
228+
extents = [(-0.5, 6.5, -0.5, 5.5),
229+
(-0.5, 6.5, 5.5, -0.5),
230+
(6.5, -0.5, -0.5, 5.5),
231+
(6.5, -0.5, 5.5, -0.5)]
232+
233+
columns = generate_imshow_demo_grid(extents)
234+
set_extent_None_text(columns['upper'][1])
235+
set_extent_None_text(columns['lower'][0])
236+
237+
238+
###############################################################################
239+
#
240+
# Explicit extent and axes limits
241+
# -------------------------------
242+
#
243+
# If we fix the axes limits by explicity setting `set_xlim` / `set_ylim`, we
244+
# force a certain size and orientation of the axes.
245+
# This can decouple the 'left-right' and 'top-bottom' sense of the image from
246+
# the orientation on the screen.
247+
#
248+
# In the example below we have chosen the limits slightly larger than the
249+
# extent (note the white areas within the Axes).
250+
#
251+
# While we keep the extents as in the examples before, the coordinate (0, 0)
252+
# is now explicitly put at the bottom left and values increase to up and to
253+
# the right (from the viewer point of view).
254+
# We can see that:
255+
#
256+
# - The coordinate ``(left, bottom)`` anchors the image which then fills the
257+
# box going towards the ``(right, top)`` point in data space.
258+
# - The first column is always closest to the 'left'.
259+
# - *origin* controls if the first row is closest to 'top' or 'bottom'.
260+
# - The image may be inverted along either direction.
261+
# - The 'left-right' and 'top-bottom' sense of the image may be uncoupled from
262+
# the orientation on the screen.
263+
264+
generate_imshow_demo_grid(extents=[None] + extents,
265+
xlim=(-2, 8), ylim=(-1, 6))

0 commit comments

Comments
 (0)