|
| 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