matplotlib-scalebar icon indicating copy to clipboard operation
matplotlib-scalebar copied to clipboard

Save image in original resolution

Open kolibril13 opened this issue 4 years ago • 5 comments

Hi! I want to save my plot with the ScaleBar, here is my code:

fig, ax = plt.subplots()
ax.axis("off")
ax.imshow(my_slice3, cmap="gray")
scalebar = ScaleBar(650, "nm", length_fraction=0.25, location= "lower right")
ax.add_artist(scalebar)
plt.savefig('foo.png')

When I save the image, the resolution is not anymore the resolution of the original image. At Stack Overflow I found the hint, that one can use imsave , but that is not working with the Scalebar. https://stackoverflow.com/questions/31544130/saving-an-imshow-like-image-while-preserving-resolution I know that I can increase the resolution e.g. with plt.rcParams['figure.dpi'] = 300 . But that is not leading to the exact pixel-by-pixel image. Any idea how to solve this?

kolibril13 avatar Aug 09 '21 16:08 kolibril13

Hi @kolibril13!

I won't say it's not possible, but I've never seen a way to do it. I say this as a daily user of Matplotlib. savefig() is ment to store the figure with labels, annotations, scalebars (!), and much much more (documentation), while imsave() cannot store any of those things (documentation).

Could I ask why you would want this? I assume the scalebar would look quite pixelated?

hakonanes avatar Aug 09 '21 18:08 hakonanes

hello @hakonanes !

Thanks for your explanation. What I am looking for is to use savefig() with my image and ScaleBar then. The reason I want to do this: I want to show images with a scalebar on a poster. As these images have a resolution about 2000x2000 pixel, I don't want to lose image quality when adding the scalebar. As I am making the poster with a vector program, I can simply save the image as a pdf. The image is then a raster image, and the Scalebar is still in vector format.

kolibril13 avatar Aug 10 '21 08:08 kolibril13

As these images have a resolution about 2000x2000 pixel, I don't want to lose image quality when adding the scalebar.

Aha, the scalebar would not be pixelated, then, wrong assumption on my end...

As I am making the poster with a vector program, I can simply save the image as a pdf. The image is then a raster image, and the Scalebar is still in vector format.

I would increase the dots per inch (DPI), as you do, to ensure that you don't loose much pixel resolution. Apart from that, I don't know what else can help, unfortunately. It might be that just adding a custom scalebar in your program yourself is the simplest solution when you want to keep the exact image resolution.

hakonanes avatar Aug 10 '21 08:08 hakonanes

Here is a solution I found:

  1. Find out display dpi here: https://dpi.lv/
  2. Run this script:
import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    scalebar = ScaleBar(650, "nm", length_fraction=0.25, location=  "lower right")
    ax.add_artist(scalebar)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()
        
export_figure_matplotlib(my_slice3, "hello2.pdf", dpi=298, resize_fact=1, plt_show=True)

Inspired by https://stackoverflow.com/a/51438809/6933377

kolibril13 avatar Aug 10 '21 08:08 kolibril13

Thank you for sharing the code. I would propose to add this snippet to the README. Can I clarify two things?

  • Why call fig.set_size_inches instead of passing the size to plt.figure? dpi can also be passed in there instead of when calling savefig
plt.figure(figsize=(image_width / dpi, image_height / dpi), frameon=False, dpi=dpi)
  • The "full axis" can also be created directly from the figure.
ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])

So I would go with this snippet if @kolibril13 agrees:

fig = plt.figure(figsize=(arr.shape[1] / dpi, arr.shape[0] / dpi), frameon=False, dpi=dpi)

ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
ax.set_axis_off()

ax.imshow(arr)

scalebar = ScaleBar(650, "nm", length_fraction=0.25, location=  "lower right")
ax.add_artist(scalebar)

ppinard avatar Nov 27 '21 15:11 ppinard