From 1124ec966371a789e398df667d01bdfce95d9eca Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 3 Apr 2018 00:13:47 -0400 Subject: [PATCH 1/4] DOC: add tutorial explaining imshow *origin* and *extent* --- tutorials/intermediate/imshow_extent.py | 179 ++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 tutorials/intermediate/imshow_extent.py diff --git a/tutorials/intermediate/imshow_extent.py b/tutorials/intermediate/imshow_extent.py new file mode 100644 index 000000000000..f0f72bc29ee1 --- /dev/null +++ b/tutorials/intermediate/imshow_extent.py @@ -0,0 +1,179 @@ +""" +*origin* and *extent* in `~.Axes.imshow` +======================================== + +:meth:`~.Axes.imshow` allows you to render an image (either a 2D array +which will be color-mapped (based on *norm* and *cmap*) or and 3D RGB(A) +array which will be used as-is) to a rectangular region in dataspace. +The orientation of the image in the final rendering is controlled by +the *origin* and *extent* kwargs (and attributes on the resulting +`~.AxesImage` instance) and the data limits of the axes. + +The *extent* kwarg controls the bounding box in data coordinates that +the image will fill specified as ``(left, right, bottom, top)`` in +**data coordinates**, the *origin* kwarg controls how the image fills +that bounding box, and the orientation in the final rendered image is +also affected by the axes limits. +""" +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec + + +def generate_imshow_demo_grid(auto_limits, extents): + N = len(extents) + fig = plt.figure(tight_layout=True) + fig.set_size_inches(6, N * (11.25) / 5) + gs = GridSpec(N, 5, figure=fig) + + columns = {'label': [fig.add_subplot(gs[j, 0]) for j in range(N)], + 'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)], + 'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]} + + d = np.arange(42).reshape(6, 7) + + for origin in ['upper', 'lower']: + for ax, extent in zip(columns[origin], extents): + + im = ax.imshow(d, origin=origin, extent=extent) + left, right, bottom, top = im.get_extent() + arrow_style = {'arrowprops': {'arrowstyle': '-|>', + 'shrinkA': 0, + 'color': '0.5', + 'linewidth': 3}} + ax.annotate('', + (left, bottom + 2*np.sign(top - bottom)), + (left, bottom), + **arrow_style) + ax.annotate('', + (left + 2*np.sign(right - left), bottom), + (left, bottom), + **arrow_style) + + if auto_limits or top > bottom: + upper_string, lower_string = 'top', 'bottom' + else: + upper_string, lower_string = 'bottom', 'top' + + if auto_limits or left < right: + port_string, starboard_string = 'left', 'right' + else: + port_string, starboard_string = 'right', 'left' + + bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"} + ann_kwargs = {'xycoords': 'axes fraction', + 'textcoords': 'offset points', + 'bbox': bbox_kwargs} + + ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1), + ha='center', va='top', **ann_kwargs) + ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1), + ha='center', va='bottom', **ann_kwargs) + + ax.annotate(port_string, xy=(0, .5), xytext=(1, 0), + ha='left', va='center', rotation=90, + **ann_kwargs) + ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0), + ha='right', va='center', rotation=-90, + **ann_kwargs) + + ax.set_title('origin: {origin}'.format(origin=origin)) + + if not auto_limits: + ax.set_xlim(-1, 7) + ax.set_ylim(-1, 6) + + for ax, extent in zip(columns['label'], extents): + text_kwargs = {'ha': 'right', + 'va': 'center', + 'xycoords': 'axes fraction', + 'xy': (1, .5)} + if extent is None: + ax.annotate('None', **text_kwargs) + ax.set_title('`extent=`') + else: + left, right, bottom, top = extent + text = ('left: {left:0.1f}\nright: {right:0.1f}\n' + + 'bottom: {bottom:0.1f}\ntop: {top:0.1f}\n').format( + left=left, right=right, bottom=bottom, top=top) + + ax.annotate(text, **text_kwargs) + ax.axis('off') + + +extents = (None, + (-0.5, 6.5, -0.5, 5.5), + (-0.5, 6.5, 5.5, -0.5), + (6.5, -0.5, -0.5, 5.5), + (6.5, -0.5, 5.5, -0.5)) + +############################################################################### +# +# +# First, using *extent* we pick a bounding box in dataspace that the +# image will fill and then interpolate/resample the underlying data to +# fill that box. +# +# - If ``origin='lower'`` than the ``[0, 0]`` entry is closest to the +# ``(left, bottom)`` corner of the bounding box and moving closer to +# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to +# higher indexed rows and moving towards ``(right, bottom)`` moves you +# along the ``[0, :]`` axis of the array to higher indexed columns +# +# - If ``origin='upper'`` then the ``[-1, 0]`` entry is closest to the +# ``(left, bottom)`` corner of the bounding box and moving towards +# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to +# lower index rows and moving towards ``(right, bottom)`` moves you +# along the ``[-1, :]`` axis of the array to higher indexed columns +# +# To demonstrate this we will plot a linear ramp +# ``np.arange(42).reshape(6, 7)`` with varying parameters. +# + +generate_imshow_demo_grid(True, extents[:1]) + +############################################################################### +# +# If we only specify *origin* we can see why it is so named. For +# ``origin='upper'`` the ``[0, 0]`` pixel is on the upper left and for +# ``origin='lower'`` the ``[0, 0]`` pixel is in the lower left [#]_. +# The gray arrows are attached to the ``(left, bottom)`` corner of the +# image. There are two tricky things going on here: first the default +# value of *extent* depends on the value of *origin* and second the x +# and y limits are adjusted to match the extent. The default *extent* +# is ``(-0.5, numcols-0.5, numrows-0.5, -0.5)`` when ``origin == +# 'upper'`` and ``(-0.5, numcols-0.5, -0.5, numrows-0.5)`` when ``origin +# == 'lower'`` which puts the pixel centers on integer positions and the +# ``[0, 0]`` pixel at ``(0, 0)`` in dataspace. +# +# +# .. [#] The default value of *origin* is set by :rc:`image.origin` +# which defaults to ``'upper'`` to match the matrix indexing +# conventions in math and computer graphics image indexing +# conventions. + +generate_imshow_demo_grid(True, extents[1:]) + +############################################################################### +# +# If the axes is set to autoscale, then view limits of the axes are set +# to match the *extent* which ensures that the coordinate set by +# ``(left, bottom)`` is at the bottom left of the axes! However, this +# may invert the axis so they do not increase in the 'natural' direction. +# + +generate_imshow_demo_grid(False, extents) + +############################################################################### +# +# If we fix the axes limits so ``(0, 0)`` is at the bottom left and +# increases to up and to the right (from the viewer point of view) then +# we can see that: +# +# - The ``(left, bottom)`` anchors the image which then fills the +# box going towards the ``(right, top)`` point in data space. +# - The first column is always closest to the 'left'. +# - *origin* controls if the first row is closest to 'top' or 'bottom'. +# - The image may be inverted along either direction. +# - The 'left-right' and 'top-bottom' sense of the image is uncoupled from +# the orientation on the screen. From e461640dc4862260c68123a32bd39a0e1ba5bcda Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 9 Apr 2018 13:35:35 -0400 Subject: [PATCH 2/4] DOC: tweak helper method kwargs and example data --- tutorials/intermediate/imshow_extent.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tutorials/intermediate/imshow_extent.py b/tutorials/intermediate/imshow_extent.py index f0f72bc29ee1..d0f56705c014 100644 --- a/tutorials/intermediate/imshow_extent.py +++ b/tutorials/intermediate/imshow_extent.py @@ -20,7 +20,7 @@ from matplotlib.gridspec import GridSpec -def generate_imshow_demo_grid(auto_limits, extents): +def generate_imshow_demo_grid(extents, auto_limits): N = len(extents) fig = plt.figure(tight_layout=True) fig.set_size_inches(6, N * (11.25) / 5) @@ -29,8 +29,8 @@ def generate_imshow_demo_grid(auto_limits, extents): columns = {'label': [fig.add_subplot(gs[j, 0]) for j in range(N)], 'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)], 'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]} - - d = np.arange(42).reshape(6, 7) + x, y = np.ogrid[0:6, 0:7] + d = x + y for origin in ['upper', 'lower']: for ax, extent in zip(columns[origin], extents): @@ -69,7 +69,6 @@ def generate_imshow_demo_grid(auto_limits, extents): ha='center', va='top', **ann_kwargs) ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1), ha='center', va='bottom', **ann_kwargs) - ax.annotate(port_string, xy=(0, .5), xytext=(1, 0), ha='left', va='center', rotation=90, **ann_kwargs) @@ -125,12 +124,8 @@ def generate_imshow_demo_grid(auto_limits, extents): # ``(left, top)`` moves along the ``[:, 0]`` axis of the array to # lower index rows and moving towards ``(right, bottom)`` moves you # along the ``[-1, :]`` axis of the array to higher indexed columns -# -# To demonstrate this we will plot a linear ramp -# ``np.arange(42).reshape(6, 7)`` with varying parameters. -# -generate_imshow_demo_grid(True, extents[:1]) +generate_imshow_demo_grid(extents[:1], auto_limits=True) ############################################################################### # @@ -151,10 +146,6 @@ def generate_imshow_demo_grid(auto_limits, extents): # which defaults to ``'upper'`` to match the matrix indexing # conventions in math and computer graphics image indexing # conventions. - -generate_imshow_demo_grid(True, extents[1:]) - -############################################################################### # # If the axes is set to autoscale, then view limits of the axes are set # to match the *extent* which ensures that the coordinate set by @@ -162,7 +153,8 @@ def generate_imshow_demo_grid(auto_limits, extents): # may invert the axis so they do not increase in the 'natural' direction. # -generate_imshow_demo_grid(False, extents) +generate_imshow_demo_grid(extents[1:], auto_limits=True) + ############################################################################### # @@ -177,3 +169,5 @@ def generate_imshow_demo_grid(auto_limits, extents): # - The image may be inverted along either direction. # - The 'left-right' and 'top-bottom' sense of the image is uncoupled from # the orientation on the screen. + +generate_imshow_demo_grid(extents, auto_limits=False) From 285c8d2190054b4929f5f2d343ff584293ff3f6d Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 27 Apr 2018 01:21:32 +0200 Subject: [PATCH 3/4] DOC: add index labels --- tutorials/intermediate/imshow_extent.py | 94 +++++++++++++++++++++---- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/tutorials/intermediate/imshow_extent.py b/tutorials/intermediate/imshow_extent.py index d0f56705c014..d709efeb2056 100644 --- a/tutorials/intermediate/imshow_extent.py +++ b/tutorials/intermediate/imshow_extent.py @@ -20,6 +20,66 @@ from matplotlib.gridspec import GridSpec +def index_to_coordinate(index, extent, origin): + """Return the pixel center of an index.""" + left, right, bottom, top = extent + + hshift = 0.5 * np.sign(right - left) + left, right = left + hshift, right - hshift + vshift = 0.5 * np.sign(top - bottom) + bottom, top = bottom + vshift, top - vshift + + if origin == 'upper': + bottom, top = top, bottom + + return { + "[0, 0]": (left, bottom), + "[N', 0]": (left, top), + "[0, M']": (right, bottom), + "[N', M']": (right, top), + }[index] + + +def get_index_label_pos(index, extent, origin, inverted_xindex): + """ + Return the desired position and horizontal alignment of an index label. + """ + if extent is None: + extent = lookup_extent(origin) + left, right, bottom, top = extent + x, y = index_to_coordinate(index, extent, origin) + + is_x0 = index[-2:] == "0]" + halign = 'left' if is_x0 ^ inverted_xindex else 'right' + hshift = 0.5 * np.sign(left - right) + x += hshift * (1 if is_x0 else -1) + return x, y, halign + + +def get_color(index, data, cmap): + """Return the data color of an index.""" + val = { + "[0, 0]": data[0, 0], + "[0, M']": data[0, -1], + "[N', 0]": data[-1, 0], + "[N', M']": data[-1, -1], + }[index] + return cmap(val / data.max()) + + +def lookup_extent(origin): + """Return extent for label positioning when not given explicitly.""" + if origin == 'lower': + return (-0.5, 6.5, -0.5, 5.5) + else: + return (-0.5, 6.5, 5.5, -0.5) + + +def set_extent_None_text(ax): + ax.text(3, 2.5, 'equals\nextent=None', size='large', + ha='center', va='center', color='w') + + def generate_imshow_demo_grid(extents, auto_limits): N = len(extents) fig = plt.figure(tight_layout=True) @@ -37,18 +97,6 @@ def generate_imshow_demo_grid(extents, auto_limits): im = ax.imshow(d, origin=origin, extent=extent) left, right, bottom, top = im.get_extent() - arrow_style = {'arrowprops': {'arrowstyle': '-|>', - 'shrinkA': 0, - 'color': '0.5', - 'linewidth': 3}} - ax.annotate('', - (left, bottom + 2*np.sign(top - bottom)), - (left, bottom), - **arrow_style) - ax.annotate('', - (left + 2*np.sign(right - left), bottom), - (left, bottom), - **arrow_style) if auto_limits or top > bottom: upper_string, lower_string = 'top', 'bottom' @@ -57,8 +105,10 @@ def generate_imshow_demo_grid(extents, auto_limits): if auto_limits or left < right: port_string, starboard_string = 'left', 'right' + inverted_xindex = False else: port_string, starboard_string = 'right', 'left' + inverted_xindex = True bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"} ann_kwargs = {'xycoords': 'axes fraction', @@ -78,6 +128,13 @@ def generate_imshow_demo_grid(extents, auto_limits): ax.set_title('origin: {origin}'.format(origin=origin)) + for index in ["[0, 0]", "[0, M']", "[N', 0]", "[N', M']"]: + tx, ty, halign = get_index_label_pos(index, extent, origin, + inverted_xindex) + facecolor = get_color(index, d, im.get_cmap()) + ax.text(tx, ty, index, color='white', ha=halign, va='center', + bbox={'boxstyle': 'square', 'facecolor': facecolor}) + if not auto_limits: ax.set_xlim(-1, 7) ax.set_ylim(-1, 6) @@ -89,7 +146,7 @@ def generate_imshow_demo_grid(extents, auto_limits): 'xy': (1, .5)} if extent is None: ax.annotate('None', **text_kwargs) - ax.set_title('`extent=`') + ax.set_title('extent=') else: left, right, bottom, top = extent text = ('left: {left:0.1f}\nright: {right:0.1f}\n' + @@ -98,6 +155,7 @@ def generate_imshow_demo_grid(extents, auto_limits): ax.annotate(text, **text_kwargs) ax.axis('off') + return columns extents = (None, @@ -106,6 +164,8 @@ def generate_imshow_demo_grid(extents, auto_limits): (6.5, -0.5, -0.5, 5.5), (6.5, -0.5, 5.5, -0.5)) + + ############################################################################### # # @@ -153,7 +213,9 @@ def generate_imshow_demo_grid(extents, auto_limits): # may invert the axis so they do not increase in the 'natural' direction. # -generate_imshow_demo_grid(extents[1:], auto_limits=True) +columns = generate_imshow_demo_grid(extents[1:], auto_limits=True) +set_extent_None_text(columns['upper'][1]) +set_extent_None_text(columns['lower'][0]) ############################################################################### @@ -170,4 +232,6 @@ def generate_imshow_demo_grid(extents, auto_limits): # - The 'left-right' and 'top-bottom' sense of the image is uncoupled from # the orientation on the screen. -generate_imshow_demo_grid(extents, auto_limits=False) +columns = generate_imshow_demo_grid(extents, auto_limits=False) +set_extent_None_text(columns['upper'][2]) +set_extent_None_text(columns['lower'][1]) From 206c3cab70d9de0f56a79610fdb4a414f09e62b1 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 27 Apr 2018 02:55:29 +0200 Subject: [PATCH 4/4] DOC: Rewrite parts of imshow origin/extent handling --- doc/_static/mpl.css | 2 +- tutorials/intermediate/imshow_extent.py | 234 +++++++++++++----------- 2 files changed, 132 insertions(+), 104 deletions(-) diff --git a/doc/_static/mpl.css b/doc/_static/mpl.css index 4e24cf579bfa..427d3c533f2b 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -416,7 +416,7 @@ div.deprecated span.versionmodified { font-weight: bold; } -div.green { +div.green, div.hint { color: #468847; background-color: #dff0d8; border: 1px solid #d6e9c6; diff --git a/tutorials/intermediate/imshow_extent.py b/tutorials/intermediate/imshow_extent.py index d709efeb2056..2e640045096a 100644 --- a/tutorials/intermediate/imshow_extent.py +++ b/tutorials/intermediate/imshow_extent.py @@ -14,6 +14,13 @@ **data coordinates**, the *origin* kwarg controls how the image fills that bounding box, and the orientation in the final rendered image is also affected by the axes limits. + +.. hint:: Most of the code below is used for adding labels and informative + text to the plots. The described effects of *origin* and *extent* can be + seen in the plots without the need to follow all code details. + + For a quick understanding, you may want to skip the code details below and + directly continue with the discussion of the results. """ import numpy as np import matplotlib.pyplot as plt @@ -34,9 +41,9 @@ def index_to_coordinate(index, extent, origin): return { "[0, 0]": (left, bottom), - "[N', 0]": (left, top), - "[0, M']": (right, bottom), - "[N', M']": (right, top), + "[M', 0]": (left, top), + "[0, N']": (right, bottom), + "[M', N']": (right, top), }[index] @@ -60,9 +67,9 @@ def get_color(index, data, cmap): """Return the data color of an index.""" val = { "[0, 0]": data[0, 0], - "[0, M']": data[0, -1], - "[N', 0]": data[-1, 0], - "[N', M']": data[-1, -1], + "[0, N']": data[0, -1], + "[M', 0]": data[-1, 0], + "[M', N']": data[-1, -1], }[index] return cmap(val / data.max()) @@ -80,7 +87,52 @@ def set_extent_None_text(ax): ha='center', va='center', color='w') -def generate_imshow_demo_grid(extents, auto_limits): +def plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim): + """Actually run ``imshow()`` and add extent and index labels.""" + im = ax.imshow(data, origin=origin, extent=extent) + + # extent labels (left, right, bottom, top) + left, right, bottom, top = im.get_extent() + if xlim is None or top > bottom: + upper_string, lower_string = 'top', 'bottom' + else: + upper_string, lower_string = 'bottom', 'top' + if ylim is None or left < right: + port_string, starboard_string = 'left', 'right' + inverted_xindex = False + else: + port_string, starboard_string = 'right', 'left' + inverted_xindex = True + bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"} + ann_kwargs = {'xycoords': 'axes fraction', + 'textcoords': 'offset points', + 'bbox': bbox_kwargs} + ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1), + ha='center', va='top', **ann_kwargs) + ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1), + ha='center', va='bottom', **ann_kwargs) + ax.annotate(port_string, xy=(0, .5), xytext=(1, 0), + ha='left', va='center', rotation=90, + **ann_kwargs) + ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0), + ha='right', va='center', rotation=-90, + **ann_kwargs) + ax.set_title('origin: {origin}'.format(origin=origin)) + + # index labels + for index in ["[0, 0]", "[0, N']", "[M', 0]", "[M', N']"]: + tx, ty, halign = get_index_label_pos(index, extent, origin, + inverted_xindex) + facecolor = get_color(index, data, im.get_cmap()) + ax.text(tx, ty, index, color='white', ha=halign, va='center', + bbox={'boxstyle': 'square', 'facecolor': facecolor}) + if xlim: + ax.set_xlim(*xlim) + if ylim: + ax.set_ylim(*ylim) + + +def generate_imshow_demo_grid(extents, xlim=None, ylim=None): N = len(extents) fig = plt.figure(tight_layout=True) fig.set_size_inches(6, N * (11.25) / 5) @@ -90,54 +142,11 @@ def generate_imshow_demo_grid(extents, auto_limits): 'upper': [fig.add_subplot(gs[j, 1:3]) for j in range(N)], 'lower': [fig.add_subplot(gs[j, 3:5]) for j in range(N)]} x, y = np.ogrid[0:6, 0:7] - d = x + y + data = x + y for origin in ['upper', 'lower']: for ax, extent in zip(columns[origin], extents): - - im = ax.imshow(d, origin=origin, extent=extent) - left, right, bottom, top = im.get_extent() - - if auto_limits or top > bottom: - upper_string, lower_string = 'top', 'bottom' - else: - upper_string, lower_string = 'bottom', 'top' - - if auto_limits or left < right: - port_string, starboard_string = 'left', 'right' - inverted_xindex = False - else: - port_string, starboard_string = 'right', 'left' - inverted_xindex = True - - bbox_kwargs = {'fc': 'w', 'alpha': .75, 'boxstyle': "round4"} - ann_kwargs = {'xycoords': 'axes fraction', - 'textcoords': 'offset points', - 'bbox': bbox_kwargs} - - ax.annotate(upper_string, xy=(.5, 1), xytext=(0, -1), - ha='center', va='top', **ann_kwargs) - ax.annotate(lower_string, xy=(.5, 0), xytext=(0, 1), - ha='center', va='bottom', **ann_kwargs) - ax.annotate(port_string, xy=(0, .5), xytext=(1, 0), - ha='left', va='center', rotation=90, - **ann_kwargs) - ax.annotate(starboard_string, xy=(1, .5), xytext=(-1, 0), - ha='right', va='center', rotation=-90, - **ann_kwargs) - - ax.set_title('origin: {origin}'.format(origin=origin)) - - for index in ["[0, 0]", "[0, M']", "[N', 0]", "[N', M']"]: - tx, ty, halign = get_index_label_pos(index, extent, origin, - inverted_xindex) - facecolor = get_color(index, d, im.get_cmap()) - ax.text(tx, ty, index, color='white', ha=halign, va='center', - bbox={'boxstyle': 'square', 'facecolor': facecolor}) - - if not auto_limits: - ax.set_xlim(-1, 7) - ax.set_ylim(-1, 6) + plot_imshow_with_labels(ax, data, extent, origin, xlim, ylim) for ax, extent in zip(columns['label'], extents): text_kwargs = {'ha': 'right', @@ -158,80 +167,99 @@ def generate_imshow_demo_grid(extents, auto_limits): return columns -extents = (None, - (-0.5, 6.5, -0.5, 5.5), - (-0.5, 6.5, 5.5, -0.5), - (6.5, -0.5, -0.5, 5.5), - (6.5, -0.5, 5.5, -0.5)) - +############################################################################### +# +# Default extent +# -------------- +# +# First, let's have a look at the default `extent=None` +generate_imshow_demo_grid(extents=[None]) ############################################################################### # +# Generally, for an array of shape (M, N), the first index runs along the +# vertical, the second index runs along the horizontal. +# The pixel centers are at integer positions ranging from 0 to ``N' = N - 1`` +# horizontally and from 0 to ``M' = M - 1`` vertically. +# *origin* determines how to the data is filled in the bounding box. # -# First, using *extent* we pick a bounding box in dataspace that the -# image will fill and then interpolate/resample the underlying data to -# fill that box. +# For ``origin='lower'``: # -# - If ``origin='lower'`` than the ``[0, 0]`` entry is closest to the -# ``(left, bottom)`` corner of the bounding box and moving closer to -# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to -# higher indexed rows and moving towards ``(right, bottom)`` moves you -# along the ``[0, :]`` axis of the array to higher indexed columns +# - [0, 0] is at (left, bottom) +# - [M', 0] is at (left, top) +# - [0, N'] is at (right, bottom) +# - [M', N'] is at (right, top) # -# - If ``origin='upper'`` then the ``[-1, 0]`` entry is closest to the -# ``(left, bottom)`` corner of the bounding box and moving towards -# ``(left, top)`` moves along the ``[:, 0]`` axis of the array to -# lower index rows and moving towards ``(right, bottom)`` moves you -# along the ``[-1, :]`` axis of the array to higher indexed columns - -generate_imshow_demo_grid(extents[:1], auto_limits=True) - -############################################################################### +# ``origin='upper'`` reverses the vertical axes direction and filling: +# +# - [0, 0] is at (left, top) +# - [M', 0] is at (left, bottom) +# - [0, N'] is at (right, top) +# - [M', N'] is at (right, bottom) +# +# In summary, the position of the [0, 0] index as well as the extent are +# influenced by *origin*: +# +# ====== =============== ========================================== +# origin [0, 0] position extent +# ====== =============== ========================================== +# upper top left ``(-0.5, numcols-0.5, numrows-0.5, -0.5)`` +# lower bottom left ``(-0.5, numcols-0.5, -0.5, numrows-0.5)`` +# ====== =============== ========================================== +# +# The default value of *origin* is set by :rc:`image.origin` which defaults +# to ``'upper'`` to match the matrix indexing conventions in math and +# computer graphics image indexing conventions. +# +# +# Explicit extent +# --------------- # -# If we only specify *origin* we can see why it is so named. For -# ``origin='upper'`` the ``[0, 0]`` pixel is on the upper left and for -# ``origin='lower'`` the ``[0, 0]`` pixel is in the lower left [#]_. -# The gray arrows are attached to the ``(left, bottom)`` corner of the -# image. There are two tricky things going on here: first the default -# value of *extent* depends on the value of *origin* and second the x -# and y limits are adjusted to match the extent. The default *extent* -# is ``(-0.5, numcols-0.5, numrows-0.5, -0.5)`` when ``origin == -# 'upper'`` and ``(-0.5, numcols-0.5, -0.5, numrows-0.5)`` when ``origin -# == 'lower'`` which puts the pixel centers on integer positions and the -# ``[0, 0]`` pixel at ``(0, 0)`` in dataspace. -# -# -# .. [#] The default value of *origin* is set by :rc:`image.origin` -# which defaults to ``'upper'`` to match the matrix indexing -# conventions in math and computer graphics image indexing -# conventions. -# -# If the axes is set to autoscale, then view limits of the axes are set +# By setting *extent* we define the coordinates of the image area. The +# underlying image data is interpolated/resampled to fill that area. +# +# If the axes is set to autoscale, then the view limits of the axes are set # to match the *extent* which ensures that the coordinate set by # ``(left, bottom)`` is at the bottom left of the axes! However, this # may invert the axis so they do not increase in the 'natural' direction. # -columns = generate_imshow_demo_grid(extents[1:], auto_limits=True) +extents = [(-0.5, 6.5, -0.5, 5.5), + (-0.5, 6.5, 5.5, -0.5), + (6.5, -0.5, -0.5, 5.5), + (6.5, -0.5, 5.5, -0.5)] + +columns = generate_imshow_demo_grid(extents) set_extent_None_text(columns['upper'][1]) set_extent_None_text(columns['lower'][0]) ############################################################################### # -# If we fix the axes limits so ``(0, 0)`` is at the bottom left and -# increases to up and to the right (from the viewer point of view) then -# we can see that: +# Explicit extent and axes limits +# ------------------------------- +# +# If we fix the axes limits by explicity setting `set_xlim` / `set_ylim`, we +# force a certain size and orientation of the axes. +# This can decouple the 'left-right' and 'top-bottom' sense of the image from +# the orientation on the screen. +# +# In the example below we have chosen the limits slightly larger than the +# extent (note the white areas within the Axes). +# +# While we keep the extents as in the examples before, the coordinate (0, 0) +# is now explicitly put at the bottom left and values increase to up and to +# the right (from the viewer point of view). +# We can see that: # -# - The ``(left, bottom)`` anchors the image which then fills the +# - The coordinate ``(left, bottom)`` anchors the image which then fills the # box going towards the ``(right, top)`` point in data space. # - The first column is always closest to the 'left'. # - *origin* controls if the first row is closest to 'top' or 'bottom'. # - The image may be inverted along either direction. -# - The 'left-right' and 'top-bottom' sense of the image is uncoupled from +# - The 'left-right' and 'top-bottom' sense of the image may be uncoupled from # the orientation on the screen. -columns = generate_imshow_demo_grid(extents, auto_limits=False) -set_extent_None_text(columns['upper'][2]) -set_extent_None_text(columns['lower'][1]) +generate_imshow_demo_grid(extents=[None] + extents, + xlim=(-2, 8), ylim=(-1, 6))