|
| 1 | +""" |
| 2 | +========================================= |
| 3 | +Placing images, preserving relative sizes |
| 4 | +========================================= |
| 5 | +
|
| 6 | +By default Matplotlib resamples images created with `~.Axes.imshow` to |
| 7 | +fit inside the parent `~.axes.Axes`. This can mean that images that have very |
| 8 | +different original sizes can end up appearing similar in size. |
| 9 | +
|
| 10 | +This example shows how to keep the images the same relative size, or |
| 11 | +how to make the images keep exactly the same pixels as the original data. |
| 12 | +
|
| 13 | +Preserving relative sizes |
| 14 | +========================= |
| 15 | +
|
| 16 | +By default the two images are made a similar size, despite one being 1.5 times the width |
| 17 | +of the other: |
| 18 | +""" |
| 19 | + |
| 20 | +# sphinx_gallery_thumbnail_number = -1 |
| 21 | + |
| 22 | +importmatplotlib.pyplotasplt |
| 23 | +importnumpyasnp |
| 24 | + |
| 25 | +importmatplotlib.patchesasmpatches |
| 26 | + |
| 27 | +# make the data: |
| 28 | +N=450 |
| 29 | +x=np.arange(N)/N |
| 30 | +y=np.arange(N)/N |
| 31 | + |
| 32 | +X,Y=np.meshgrid(x,y) |
| 33 | +R=np.sqrt(X**2+Y**2) |
| 34 | +f0=5 |
| 35 | +k=100 |
| 36 | +a=np.sin(np.pi*2* (f0*R+k*R**2/2)) |
| 37 | +A=a[:100, :300] |
| 38 | +B=A[:40, :200] |
| 39 | + |
| 40 | +# default layout: both axes have the same size |
| 41 | +fig,axs=plt.subplots(1,2,facecolor='aliceblue') |
| 42 | + |
| 43 | +axs[0].imshow(A,vmin=-1,vmax=1) |
| 44 | +axs[1].imshow(B,vmin=-1,vmax=1) |
| 45 | + |
| 46 | + |
| 47 | +defannotate_rect(ax): |
| 48 | +# add a rectangle that is the size of the B matrix |
| 49 | +rect=mpatches.Rectangle((0,0),200,40,linewidth=1, |
| 50 | +edgecolor='r',facecolor='none') |
| 51 | +ax.add_patch(rect) |
| 52 | +returnrect |
| 53 | + |
| 54 | +annotate_rect(axs[0]) |
| 55 | + |
| 56 | +# %% |
| 57 | +# Note that both images have an aspect ratio of 1 (i.e. pixels are square), but |
| 58 | +# pixels sizes differ because the images are scaled to the same width. |
| 59 | +# |
| 60 | +# If the size of the images are amenable, we can preserve the relative sizes of two |
| 61 | +# images by using either the *width_ratio* or *height_ratio* of the subplots. Which |
| 62 | +# one you use depends on the shape of the image and the size of the figure. |
| 63 | +# We can control the relative sizes using the *width_ratios* argument *if* the images |
| 64 | +# are wider than they are tall and shown side by side, as is the case here. |
| 65 | +# |
| 66 | +# While we are making changes, let us also make the aspect ratio of the figure closer |
| 67 | +# to the aspect ratio of the axes using *figsize* so that the figure does not have so |
| 68 | +# much white space. Note that you could alternatively trim extra blank space when |
| 69 | +# saving a figure by passing ``bbox_inches="tight"`` to `~.Figure.savefig`. |
| 70 | + |
| 71 | +fig,axs=plt.subplots(1,2,width_ratios=[300/200,1], |
| 72 | +figsize=(6.4,2),facecolor='aliceblue') |
| 73 | + |
| 74 | +axs[0].imshow(A,vmin=-1,vmax=1) |
| 75 | +annotate_rect(axs[0]) |
| 76 | + |
| 77 | +axs[1].imshow(B,vmin=-1,vmax=1) |
| 78 | +# %% |
| 79 | +# Given that the data subsample is in the upper left of the larger image, |
| 80 | +# it might make sense if the top of the smaller Axes aligned with the top of the larger. |
| 81 | +# This can be done manually by using `~.Axes.set_anchor`, and using "NW" (for |
| 82 | +# northwest). |
| 83 | + |
| 84 | +fig,axs=plt.subplots(1,2,width_ratios=[300/200,1], |
| 85 | +figsize=(6.4,2),facecolor='aliceblue') |
| 86 | + |
| 87 | +axs[0].imshow(A,vmin=-1,vmax=1) |
| 88 | +annotate_rect(axs[0]) |
| 89 | + |
| 90 | +axs[0].set_anchor('NW') |
| 91 | +axs[1].imshow(B,vmin=-1,vmax=1) |
| 92 | +axs[1].set_anchor('NW') |
| 93 | + |
| 94 | +# %% |
| 95 | +# Explicit placement |
| 96 | +# ================== |
| 97 | +# The above approach with adjusting ``figsize`` and ``width_ratios`` does |
| 98 | +# not generalize well, because it needs manual parameter tuning, and |
| 99 | +# possibly even code changes to using ``height_ratios`` instead of |
| 100 | +# ``width_ratios`` depending on the aspects and layout of the images. |
| 101 | +# |
| 102 | +# We can alternative calculate positions explicitly and place Axes at absolute |
| 103 | +# coordinates using `~.Figure.add_axes`. This takes the position in the form |
| 104 | +# ``[left bottom width height]`` and is in |
| 105 | +# :ref:`figure coordinates <transforms_tutorial>`. In the following, we |
| 106 | +# determine figure size and Axes positions so that one image data point |
| 107 | +# is rendered exactly to one figure pixel. |
| 108 | + |
| 109 | +dpi=100# 100 pixels is one inch |
| 110 | + |
| 111 | +# All variables from here are in pixels: |
| 112 | +buffer=0.35*dpi# pixels |
| 113 | + |
| 114 | +# Get the position of A axes |
| 115 | +left=buffer |
| 116 | +bottom=buffer |
| 117 | +ny,nx=np.shape(A) |
| 118 | +posA= [left,bottom,nx,ny] |
| 119 | +# we know this is tallest, so we can already get the fig height (in pixels) |
| 120 | +fig_height=bottom+ny+buffer |
| 121 | + |
| 122 | +# place the B axes to the right of the A axes |
| 123 | +left=left+nx+buffer |
| 124 | + |
| 125 | +ny,nx=np.shape(B) |
| 126 | +# align the bottom so that the top lines up with the top of the A axes: |
| 127 | +bottom=fig_height-buffer-ny |
| 128 | +posB= [left,bottom,nx,ny] |
| 129 | + |
| 130 | +# now we can get the fig width (in pixels) |
| 131 | +fig_width=left+nx+buffer |
| 132 | + |
| 133 | +# figsize must be in inches: |
| 134 | +fig=plt.figure(figsize=(fig_width/dpi,fig_height/dpi),facecolor='aliceblue') |
| 135 | + |
| 136 | +# the position posA must be normalized by the figure width and height: |
| 137 | +ax=fig.add_axes([posA[0]/fig_width,posA[1]/fig_height, |
| 138 | +posA[2]/fig_width,posA[3]/fig_height]) |
| 139 | +ax.imshow(A,vmin=-1,vmax=1) |
| 140 | +annotate_rect(ax) |
| 141 | + |
| 142 | +ax=fig.add_axes([posB[0]/fig_width,posB[1]/fig_height, |
| 143 | +posB[2]/fig_width,posB[3]/fig_height]) |
| 144 | +ax.imshow(B,vmin=-1,vmax=1) |
| 145 | +plt.show() |
| 146 | +# %% |
| 147 | +# Inspection of the image will show that it is exactly 3* 35 + 300 + 200 = 605 |
| 148 | +# pixels wide, and 2 * 35 + 100 = 170 pixels high (or twice that if the 2x |
| 149 | +# version is used by the browser instead). The images should be rendered with |
| 150 | +# exactly 1 pixel per data point (or four, if 2x). |
| 151 | +# |
| 152 | +# .. admonition:: References |
| 153 | +# |
| 154 | +# The use of the following functions, methods, classes and modules is shown |
| 155 | +# in this example: |
| 156 | +# |
| 157 | +# - `matplotlib.axes.Axes.imshow` |
| 158 | +# - `matplotlib.figure.Figure.add_axes` |
| 159 | +# |
| 160 | +# .. tags:: |
| 161 | +# |
| 162 | +# component: figure |
| 163 | +# component: axes |
| 164 | +# styling: position |
| 165 | +# plot-type: image |