Output images of Matplotlib figures have different sizes across operating systems

Viewed 375

One of our tests is failing because the output image is sometimes a slightly different size. On Linux it is 579x517 pixels and on Windows it is 582x520 pixels. I have checked the versions of matplotlib and pandas and they are the same. There are slight differences in matplotlib.rcParams; I've tried changing the parameters that look relevant, but it didn't help.

Is it because the display size is slightly different on the two machines? If I set dpi=99.99 on the savefig on the Windows machine, the output size is 579x517 pixels. I don't want to change this in the application though as it is a bit of a hack just to fix a failing test.

This is the plot function we're using, percentages is a pandas dataframe.

def plot_and_save(percentages, output_directory, filename, title, legend, x_label, y_label,
                  font_size=16):
    """Plot data and save figure to file."""
    matplotlib.rcParams['font.size'] = font_size
    ax1 = percentages.plot.bar(color=['#484D7A', '#F6A124'])
    ax1.grid(which='major', axis='y', color='#4A4A49', alpha=0.1)
    ax1.set_axisbelow(True)  # Puts gridlines below the bars
    ax1.set(xlabel=x_label, ylabel=y_label, title=title)
    ax1.legend(legend, framealpha=1)

    output_path = os.path.join(output_directory, filename)

    plt.savefig(output_path, bbox_inches='tight')
    plt.close()

EDIT: I have a simpler repro case for this. Running this code on my Linux machine, I get an image of 576x455 pixels.

    plt.plot(1)
    plt.title('Title')
    plt.xlabel('Label')
    plt.ylabel('Label')
    plt.savefig('test.png', bbox_inches='tight')

The Windows machine produces an image of 576x453 pixels, so if I divide the expected pixel size by the actual pixel size then alter the default image size by that ratio, it should produce an image of the right size.

    exp_x, exp_y = 576, 455

    plt.plot(1)
    fig = plt.gcf()
    def_size = fig.get_size_inches()
    print('default size', def_size)
    new_size = (def_size[0] * exp_x / act_x, def_size[1] * exp_y / act_y)
    print('new size', new_size)
    fig.set_size_inches(*new_size)
    plt.title('Title')
    plt.xlabel('Label')
    plt.ylabel('Label')
    plt.savefig('test.png', bbox_inches='tight')

The output is a 576x454 pixel image, so there must be some rounding going on.

default size [6.4 4.8]
new size (6.4, 4.821192052980132)
1 Answers

This is unexpected behavior. Matplotlib is designed to produce pixel-identical output images on all platforms.

I cannot reproduce the behavior that you describe. Using your minimal example, I get byte-for-byte identical .png images on Linux (Ubuntu 20.04 LTS) and Windows (10), using Matplotlib 3.4.2 on either platform. The resolution is 576×455 pixels, which is what you report for Linux, but not Windows.

For basic tests such as creating a simple figure (like in your example), the Matplotlib test suite renders them on the test system and then compares the final output to a "baseline" image in the source-code repository, such as this simple figure. See "Writing an image comparison test" in the Matplotlib Developer's Guide. The test fixture that handles the comparison has a default tolerance of zero, which is overridden for certain tests, but not for that simple figure. It then just uses numpy.array_equal to compare actual and expected image. In other words: There should be no difference, especially not in resolution. These tests are routinely run on Linux, macOS, and Windows.

So the behavior you describe is highly unexpected and can only be explained by the differences of default values that you hinted at, in matplotlib.rcParams. Arguably the cleanest solution is to use the exact configuration that Matplotlib ships with and adapt your plot routines accordingly. Otherwise consider running the Matplotlib test suite on both systems to narrow down the deviation. Your Windows installation is the more likely culprit.

Related