Search code examples
pythonimagematplotlibcolorbar

Maintain original image resolution in plot with colorbar


I have a greyscale image of some dimensions (height x width). I would like to produce a plot of this image along with a colorbar aligned with the image so that the resolution of the image in this plot is the same as the original resolution. Here's a code which takes a 100 x 50 image and produces a plot with colorbar:

import numpy as np
import matplotlib.pyplot as plt

img_height, img_width = 100, 50
fig, ax = plt.subplots()
ax.set_axis_off()

image = ax.imshow(
    np.arange(img_height * img_width).reshape((img_height, img_width)),
    cmap="hot"
)
colorbar = fig.colorbar(image)
colorbar.set_label('label')

plt.savefig('output.png', bbox_inches='tight')

The produced plot has dimensions 398 x 312. There's some padding involved but the image in the plot certainly doesn't have 100 x 50 resolution as the original, but higher.

enter image description here

I tried tinkering with figsize in fig, ax = plt.subplots(figsize=(...)) and dpi in plt.savefig('output.png', dpi=..., bbox_inches='tight'), but the plots get all messed up:

enter image description here

Is there a way to make this work in matplotlib? I'm looking for something like this solution which includes a colorbar:

Matplotlib - unable to save image in same resolution as original image

I looked at the following issues which are related but the answers did not help with my particular issue:

How do I maintain image size when using a colorbar?

colorbar changes the size of subplot in python

Set Matplotlib colorbar size to match graph


Solution

  • Don't rely on automated tools if need exact dimensions. The following hard codes all the positions, but the resulting image will have exactly the size of the original image. If you want to add larger margins, etc, you can easily tweak the positions of the axes to do that.

    import numpy as np
    import matplotlib.pyplot as plt
    
    img_height, img_width = 200, 100
    dpi = 100
    
    fig = plt.figure(dpi=100, figsize=(img_width/dpi*2, img_height/dpi))
    # just to make it clear where the figure is on a white background:
    fig.set_facecolor('cornflowerblue')
    
    ax = fig.add_axes([0, 0, 1/2, 1])
    ax.set_axis_off()
    
    image = ax.imshow(
        np.arange(img_height * img_width).reshape((img_height, img_width)),
        cmap="hot"
    )
    
    cax = fig.add_axes([1/2+0.02, 0.2, 0.1, 0.6])
    colorbar = fig.colorbar(image, cax=cax)
    colorbar.set_label('label')
    
    plt.savefig('output.png', dpi=100)
    

    The resulting figure is 200 x 200, where I had to make it quite a bit wider to accommodate the labelling on the colorbar.

    enter image description here