Skip to content

Commit 206c3ca

Browse files
committed
DOC: Rewrite parts of imshow origin/extent handling
1 parent 285c8d2 commit 206c3ca

File tree

2 files changed

+132
-104
lines changed

2 files changed

+132
-104
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;

tutorials/intermediate/imshow_extent.py

Lines changed: 131 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
**data coordinates**, the *origin* kwarg controls how the image fills
1515
that bounding box, and the orientation in the final rendered image is
1616
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.
1724
"""
1825
import numpy as np
1926
import matplotlib.pyplot as plt
@@ -34,9 +41,9 @@ def index_to_coordinate(index, extent, origin):
3441

3542
return {
3643
"[0, 0]": (left, bottom),
37-
"[N', 0]": (left, top),
38-
"[0, M']": (right, bottom),
39-
"[N', M']": (right, top),
44+
"[M', 0]": (left, top),
45+
"[0, N']": (right, bottom),
46+
"[M', N']": (right, top),
4047
}[index]
4148

4249

@@ -60,9 +67,9 @@ def get_color(index, data, cmap):
6067
"""Return the data color of an index."""
6168
val = {
6269
"[0, 0]": data[0, 0],
63-
"[0, M']": data[0, -1],
64-
"[N', 0]": data[-1, 0],
65-
"[N', M']": data[-1, -1],
70+
"[0, N']": data[0, -1],
71+
"[M', 0]": data[-1, 0],
72+
"[M', N']": data[-1, -1],
6673
}[index]
6774
return cmap(val / data.max())
6875

@@ -80,7 +87,52 @@ def set_extent_None_text(ax):
8087
ha='center', va='center', color='w')
8188

8289

83-
def generate_imshow_demo_grid(extents, auto_limits):
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):
84136
N = len(extents)
85137
fig = plt.figure(tight_layout=True)
86138
fig.set_size_inches(6, N * (11.25) / 5)
@@ -90,54 +142,11 @@ def generate_imshow_demo_grid(extents, auto_limits):
90142
'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)],
91143
'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]}
92144
x, y = np.ogrid[0:6, 0:7]
93-
d = x + y
145+
data = x + y
94146

95147
for origin in ['upper', 'lower']:
96148
for ax, extent in zip(columns[origin], extents):
97-
98-
im = ax.imshow(d, origin=origin, extent=extent)
99-
left, right, bottom, top = im.get_extent()
100-
101-
if auto_limits or top > bottom:
102-
upper_string, lower_string = 'top', 'bottom'
103-
else:
104-
upper_string, lower_string = 'bottom', 'top'
105-
106-
if auto_limits or left < right:
107-
port_string, starboard_string = 'left', 'right'
108-
inverted_xindex = False
109-
else:
110-
port_string, starboard_string = 'right', 'left'
111-
inverted_xindex = True
112-
113-
bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"}
114-
ann_kwargs = {'xycoords': 'axes fraction',
115-
'textcoords': 'offset points',
116-
'bbox': bbox_kwargs}
117-
118-
ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1),
119-
ha='center', va='top', **ann_kwargs)
120-
ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1),
121-
ha='center', va='bottom', **ann_kwargs)
122-
ax.annotate(port_string, xy=(0, .5), xytext=(1, 0),
123-
ha='left', va='center', rotation=90,
124-
**ann_kwargs)
125-
ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0),
126-
ha='right', va='center', rotation=-90,
127-
**ann_kwargs)
128-
129-
ax.set_title('origin: {origin}'.format(origin=origin))
130-
131-
for index in ["[0, 0]", "[0, M']", "[N', 0]", "[N', M']"]:
132-
tx, ty, halign = get_index_label_pos(index, extent, origin,
133-
inverted_xindex)
134-
facecolor = get_color(index, d, im.get_cmap())
135-
ax.text(tx, ty, index, color='white', ha=halign, va='center',
136-
bbox={'boxstyle': 'square', 'facecolor': facecolor})
137-
138-
if not auto_limits:
139-
ax.set_xlim(-1, 7)
140-
ax.set_ylim(-1, 6)
149+
plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim)
141150

142151
for ax, extent in zip(columns['label'], extents):
143152
text_kwargs = {'ha': 'right',
@@ -158,80 +167,99 @@ def generate_imshow_demo_grid(extents, auto_limits):
158167
return columns
159168

160169

161-
extents = (None,
162-
(-0.5, 6.5, -0.5, 5.5),
163-
(-0.5, 6.5, 5.5, -0.5),
164-
(6.5, -0.5, -0.5, 5.5),
165-
(6.5, -0.5, 5.5, -0.5))
166-
170+
###############################################################################
171+
#
172+
# Default extent
173+
# --------------
174+
#
175+
# First, let's have a look at the default `extent=None`
167176

177+
generate_imshow_demo_grid(extents=[None])
168178

169179
###############################################################################
170180
#
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.
171186
#
172-
# First, using *extent* we pick a bounding box in dataspace that the
173-
# image will fill and then interpolate/resample the underlying data to
174-
# fill that box.
187+
# For ``origin='lower'``:
175188
#
176-
# - If ``origin='lower'`` than the ``[0, 0]`` entry is closest to the
177-
# ``(left, bottom)`` corner of the bounding box and moving closer to
178-
# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to
179-
# higher indexed rows and moving towards ``(right, bottom)`` moves you
180-
# along the ``[0, :]`` axis of the array to higher indexed columns
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)
181193
#
182-
# - If ``origin='upper'`` then the ``[-1, 0]`` entry is closest to the
183-
# ``(left, bottom)`` corner of the bounding box and moving towards
184-
# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to
185-
# lower index rows and moving towards ``(right, bottom)`` moves you
186-
# along the ``[-1, :]`` axis of the array to higher indexed columns
187-
188-
generate_imshow_demo_grid(extents[:1], auto_limits=True)
189-
190-
###############################################################################
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+
# ---------------
191218
#
192-
# If we only specify *origin* we can see why it is so named. For
193-
# ``origin='upper'`` the ``[0, 0]`` pixel is on the upper left and for
194-
# ``origin='lower'`` the ``[0, 0]`` pixel is in the lower left [#]_.
195-
# The gray arrows are attached to the ``(left, bottom)`` corner of the
196-
# image. There are two tricky things going on here: first the default
197-
# value of *extent* depends on the value of *origin* and second the x
198-
# and y limits are adjusted to match the extent. The default *extent*
199-
# is ``(-0.5, numcols-0.5, numrows-0.5, -0.5)`` when ``origin ==
200-
# 'upper'`` and ``(-0.5, numcols-0.5, -0.5, numrows-0.5)`` when ``origin
201-
# == 'lower'`` which puts the pixel centers on integer positions and the
202-
# ``[0, 0]`` pixel at ``(0, 0)`` in dataspace.
203-
#
204-
#
205-
# .. [#] The default value of *origin* is set by :rc:`image.origin`
206-
# which defaults to ``'upper'`` to match the matrix indexing
207-
# conventions in math and computer graphics image indexing
208-
# conventions.
209-
#
210-
# If the axes is set to autoscale, then view limits of the axes are set
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
211223
# to match the *extent* which ensures that the coordinate set by
212224
# ``(left, bottom)`` is at the bottom left of the axes! However, this
213225
# may invert the axis so they do not increase in the 'natural' direction.
214226
#
215227

216-
columns = generate_imshow_demo_grid(extents[1:], auto_limits=True)
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)
217234
set_extent_None_text(columns['upper'][1])
218235
set_extent_None_text(columns['lower'][0])
219236

220237

221238
###############################################################################
222239
#
223-
# If we fix the axes limits so ``(0, 0)`` is at the bottom left and
224-
# increases to up and to the right (from the viewer point of view) then
225-
# we can see that:
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:
226255
#
227-
# - The ``(left, bottom)`` anchors the image which then fills the
256+
# - The coordinate ``(left, bottom)`` anchors the image which then fills the
228257
# box going towards the ``(right, top)`` point in data space.
229258
# - The first column is always closest to the 'left'.
230259
# - *origin* controls if the first row is closest to 'top' or 'bottom'.
231260
# - The image may be inverted along either direction.
232-
# - The 'left-right' and 'top-bottom' sense of the image is uncoupled from
261+
# - The 'left-right' and 'top-bottom' sense of the image may be uncoupled from
233262
# the orientation on the screen.
234263

235-
columns = generate_imshow_demo_grid(extents, auto_limits=False)
236-
set_extent_None_text(columns['upper'][2])
237-
set_extent_None_text(columns['lower'][1])
264+
generate_imshow_demo_grid(extents=[None] + extents,
265+
xlim=(-2, 8), ylim=(-1, 6))

0 commit comments

Comments
 (0)