|
| 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 | +importnumpyasnp |
| 19 | +importmatplotlib.pyplotasplt |
| 20 | +frommatplotlib.gridspecimportGridSpec |
| 21 | + |
| 22 | + |
| 23 | +defgenerate_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])forjinrange(N)], |
| 30 | +'upper': [fig.add_subplot(gs[j,1:3])forjinrange(N)], |
| 31 | +'lower': [fig.add_subplot(gs[j,3:5])forjinrange(N)]} |
| 32 | + |
| 33 | +d=np.arange(42).reshape(6,7) |
| 34 | + |
| 35 | +fororiginin ['upper','lower']: |
| 36 | +forax,extentinzip(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 | +ifauto_limitsortop>bottom: |
| 54 | +upper_string,lower_string='top','bottom' |
| 55 | +else: |
| 56 | +upper_string,lower_string='bottom','top' |
| 57 | + |
| 58 | +ifauto_limitsorleft<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 | +ifnotauto_limits: |
| 83 | +ax.set_xlim(-1,7) |
| 84 | +ax.set_ylim(-1,6) |
| 85 | + |
| 86 | +forax,extentinzip(columns['label'],extents): |
| 87 | +text_kwargs= {'ha':'right', |
| 88 | +'va':'center', |
| 89 | +'xycoords':'axes fraction', |
| 90 | +'xy': (1,.5)} |
| 91 | +ifextentisNone: |
| 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. |