|
| 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 | +importnumpyasnp |
| 26 | +importmatplotlib.pyplotasplt |
| 27 | +frommatplotlib.gridspecimportGridSpec |
| 28 | + |
| 29 | + |
| 30 | +defindex_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 | +iforigin=='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 | +defget_index_label_pos(index,extent,origin,inverted_xindex): |
| 51 | +""" |
| 52 | + Return the desired position and horizontal alignment of an index label. |
| 53 | + """ |
| 54 | +ifextentisNone: |
| 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'ifis_x0^inverted_xindexelse'right' |
| 61 | +hshift=0.5*np.sign(left-right) |
| 62 | +x+=hshift* (1ifis_x0else-1) |
| 63 | +returnx,y,halign |
| 64 | + |
| 65 | + |
| 66 | +defget_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 | +returncmap(val/data.max()) |
| 75 | + |
| 76 | + |
| 77 | +deflookup_extent(origin): |
| 78 | +"""Return extent for label positioning when not given explicitly.""" |
| 79 | +iforigin=='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 | +defset_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 | +defplot_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 | +ifxlimisNoneortop>bottom: |
| 97 | +upper_string,lower_string='top','bottom' |
| 98 | +else: |
| 99 | +upper_string,lower_string='bottom','top' |
| 100 | +ifylimisNoneorleft<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 | +forindexin ["[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 | +ifxlim: |
| 130 | +ax.set_xlim(*xlim) |
| 131 | +ifylim: |
| 132 | +ax.set_ylim(*ylim) |
| 133 | + |
| 134 | + |
| 135 | +defgenerate_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])forjinrange(N)], |
| 142 | +'upper': [fig.add_subplot(gs[j,1:3])forjinrange(N)], |
| 143 | +'lower': [fig.add_subplot(gs[j,3:5])forjinrange(N)]} |
| 144 | +x,y=np.ogrid[0:6,0:7] |
| 145 | +data=x+y |
| 146 | + |
| 147 | +fororiginin ['upper','lower']: |
| 148 | +forax,extentinzip(columns[origin],extents): |
| 149 | +plot_imshow_with_labels(ax,data,extent,origin,xlim,ylim) |
| 150 | + |
| 151 | +forax,extentinzip(columns['label'],extents): |
| 152 | +text_kwargs= {'ha':'right', |
| 153 | +'va':'center', |
| 154 | +'xycoords':'axes fraction', |
| 155 | +'xy': (1,.5)} |
| 156 | +ifextentisNone: |
| 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 | +returncolumns |
| 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)) |