Skip to content

Instantly share code, notes, and snippets.

@robertguetzkow
Last active December 7, 2019 02:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertguetzkow/f8feeef28ea8126daa23b7543bd64613 to your computer and use it in GitHub Desktop.
Save robertguetzkow/f8feeef28ea8126daa23b7543bd64613 to your computer and use it in GitHub Desktop.
Image encoding benchmark for Blender's file formats
import bpy
import os
import sys
import time
import statistics
import datetime
import random
from abc import ABC, abstractmethod
from typing import List
from contextlib import redirect_stdout
from mathutils import Vector
class FileSettings(ABC):
@abstractmethod
def activate(self):
pass
@abstractmethod
def configuration_string(self):
pass
def __hash__(self):
return hash(self.configuration_string())
def __eq__(self, other):
return self.configuration_string() == other.configuration_string()
class BMP(FileSettings):
def __init__(self, color_mode="RGB"):
allowed_settings = {
"color_mode": {"BW", "RGB"}
}
self.label = "BMP"
self.extension = ".bmp"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "BMP"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class IRIS(FileSettings):
def __init__(self, color_mode="RGBA"):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"}
}
self.label = "IRIS"
self.extension = ".rgb"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "IRIS"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class PNG(FileSettings):
def __init__(self, color_mode="RGBA", color_depth="16", compression=100):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"},
"color_depth": {"8", "16"},
"compression": (0, 100)
}
self.label = "PNG"
self.extension = ".png"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if compression >= allowed_settings["compression"][0] and compression <= allowed_settings["compression"][1]:
self.settings["compression"] = compression
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "PNG"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.compression = self.settings["compression"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"compression: {self.settings['compression']}"
return setting_str
class JPEG(FileSettings):
def __init__(self, color_mode="RGB", quality=100):
allowed_settings = {
"color_mode": {"BW", "RGB"},
"quality": (0, 100)
}
self.label = "JPEG"
self.extension = ".jpeg"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if quality >= allowed_settings["quality"][0] and quality <= allowed_settings["quality"][1]:
self.settings["quality"] = quality
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "JPEG"
image_settings.color_mode = self.settings["color_mode"]
image_settings.quality = self.settings["quality"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"quality: {self.settings['quality']}"
return setting_str
class JPEG2000(FileSettings):
def __init__(self, color_mode="RGB", color_depth="16", quality=100, jpeg2k_codec="JP2",
use_jpeg2k_cinema_preset=False, use_jpeg2k_cinema_48=False, use_jpeg2k_ycc=False):
allowed_settings = {
"color_mode": {"BW", "RGB"},
"color_depth": {"8", "12", "16"},
"quality": (0, 100),
"jpeg2k_codec": {"JP2", "J2K"},
"use_jpeg2k_cinema_preset": {True, False},
"use_jpeg2k_cinema_48": {True, False},
"use_jpeg2k_ycc": {True, False}
}
self.label = "J2K"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if quality >= allowed_settings["quality"][0] and quality <= allowed_settings["quality"][1]:
self.settings["quality"] = quality
if jpeg2k_codec in allowed_settings["jpeg2k_codec"]:
self.settings["jpeg2k_codec"] = jpeg2k_codec
if use_jpeg2k_cinema_preset in allowed_settings["use_jpeg2k_cinema_preset"]:
self.settings["use_jpeg2k_cinema_preset"] = use_jpeg2k_cinema_preset
if use_jpeg2k_cinema_48 in allowed_settings["use_jpeg2k_cinema_48"]:
self.settings["use_jpeg2k_cinema_48"] = use_jpeg2k_cinema_48
if use_jpeg2k_ycc in allowed_settings["use_jpeg2k_ycc"]:
self.settings["use_jpeg2k_ycc"] = use_jpeg2k_ycc
if self.settings["jpeg2k_codec"] == "JP2":
self.extension = ".jp2"
else:
self.extension = ".j2c"
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "JPEG2000"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.quality = self.settings["quality"]
image_settings.jpeg2k_codec = self.settings["jpeg2k_codec"]
image_settings.use_jpeg2k_cinema_preset = self.settings["use_jpeg2k_cinema_preset"]
image_settings.use_jpeg2k_cinema_48 = self.settings["use_jpeg2k_cinema_48"]
image_settings.use_jpeg2k_ycc = self.settings["use_jpeg2k_ycc"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"quality: {self.settings['quality']}, "
setting_str += f"jpeg2k_codec: {self.settings['jpeg2k_codec']}, "
setting_str += f"use_jpeg2k_cinema_preset: {self.settings['use_jpeg2k_cinema_preset']}, "
setting_str += f"use_jpeg2k_cinema_48: {self.settings['use_jpeg2k_cinema_48']}, "
setting_str += f"use_jpeg2k_ycc: {self.settings['use_jpeg2k_ycc']}"
return setting_str
class TARGA(FileSettings):
def __init__(self, color_mode="RGBA"):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"}
}
self.label = "TARGA"
self.extension = ".tga"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "TARGA"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class TARGARaw(FileSettings):
def __init__(self, color_mode="RGBA"):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"}
}
self.label = "TARGA RAW"
self.extension = ".tga"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "TARGA_RAW"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class Cineon(FileSettings):
def __init__(self, color_mode="RGB"):
allowed_settings = {
"color_mode": {"RGB"}
}
self.label = "Cineon"
self.extension = ".cin"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "CINEON"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class DPX(FileSettings):
def __init__(self, color_mode="RGBA", color_depth="16", use_cineon_log=False):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"},
"color_depth": {"8", "10", "12", "16"},
"use_cineon_log": {True, False}
}
self.label = "DPX"
self.extension = ".dpx"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if use_cineon_log in allowed_settings["use_cineon_log"]:
self.settings["use_cineon_log"] = use_cineon_log
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "DPX"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.use_cineon_log = self.settings["use_cineon_log"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"use_cineon_log: {self.settings['use_cineon_log']}"
return setting_str
class OpenEXRMultilayer(FileSettings):
def __init__(self, color_mode="RGBA", color_depth="32", exr_codec="PIZ", use_preview=False):
allowed_settings = {
"color_mode": {"RGB", "RGBA"},
"color_depth": {"16", "32"},
"exr_codec": {"DWAA", "B44A", "ZIPS", "RLE", "PIZ", "ZIP", "PXR24", "NONE"},
"use_preview": {True, False}
}
self.label = "OpenEXR Multilayer"
self.extension = ".exr"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if exr_codec in allowed_settings["exr_codec"]:
self.settings["exr_codec"] = exr_codec
if use_preview in allowed_settings["use_preview"]:
self.settings["use_preview"] = use_preview
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "OPEN_EXR_MULTILAYER"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.exr_codec = self.settings["exr_codec"]
image_settings.use_preview = self.settings["use_preview"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"exr_codec: {self.settings['exr_codec']}, "
setting_str += f"use_preview: {self.settings['use_preview']}"
return setting_str
class OpenEXR(FileSettings):
def __init__(self, color_mode="RGBA", color_depth="32", exr_codec="PIZ", use_zbuffer=False, use_preview=False):
allowed_settings = {
"color_mode": {"RGB", "RGBA"},
"color_depth": {"16", "32"},
"exr_codec": {"DWAA", "B44A", "ZIPS", "RLE", "PIZ", "ZIP", "PXR24", "NONE"},
"use_zbuffer": {True, False},
"use_preview": {True, False}
}
self.label = "OpenEXR"
self.extension = ".exr"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if exr_codec in allowed_settings["exr_codec"]:
self.settings["exr_codec"] = exr_codec
if use_zbuffer in allowed_settings["use_zbuffer"]:
self.settings["use_zbuffer"] = use_zbuffer
if use_preview in allowed_settings["use_preview"]:
self.settings["use_preview"] = use_preview
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "OPEN_EXR"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.exr_codec = self.settings["exr_codec"]
image_settings.use_zbuffer = self.settings["use_zbuffer"]
image_settings.use_preview = self.settings["use_preview"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"exr_codec: {self.settings['exr_codec']}, "
setting_str += f"use_zbuffer: {self.settings['use_zbuffer']}, "
setting_str += f"use_preview: {self.settings['use_preview']}"
return setting_str
class RadianceHDR(FileSettings):
def __init__(self, color_mode="RGB"):
allowed_settings = {
"color_mode": {"BW", "RGB"},
}
self.label = "Radiance HDR"
self.extension = ".hdr"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "HDR"
image_settings.color_mode = self.settings["color_mode"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}"
return setting_str
class TIFF(FileSettings):
def __init__(self, color_mode="RGBA", color_depth="16", tiff_codec="DEFLATE"):
allowed_settings = {
"color_mode": {"BW", "RGB", "RGBA"},
"color_depth": {"8", "16"},
"tiff_codec": {"PACKBITS", "LZW", "DEFLATE", "NONE"}
}
self.label = "TIFF"
self.extension = ".tif"
self.settings = {}
if color_mode in allowed_settings["color_mode"]:
self.settings["color_mode"] = color_mode
if color_depth in allowed_settings["color_depth"]:
self.settings["color_depth"] = color_depth
if tiff_codec in allowed_settings["tiff_codec"]:
self.settings["tiff_codec"] = tiff_codec
def activate(self):
image_settings = bpy.context.scene.render.image_settings
image_settings.file_format = "TIFF"
image_settings.color_mode = self.settings["color_mode"]
image_settings.color_depth = self.settings["color_depth"]
image_settings.tiff_codec = self.settings["tiff_codec"]
def configuration_string(self):
setting_str = self.label
setting_str += f" color_mode: {self.settings['color_mode']}, "
setting_str += f"color_depth: {self.settings['color_depth']}, "
setting_str += f"tiff_codec: {self.settings['tiff_codec']}"
return setting_str
class Measurement():
def __init__(self, filepath, file_settings: List[FileSettings]):
self.filepath = os.path.normpath(filepath)
self.filename = os.path.basename(self.filepath)
self.data = {}
self.output_size = {}
self.image_resolution = (0, 0)
for file_setting in file_settings:
self.data[file_setting] = []
def add_result(self, file_settings, time, size):
self.data[file_settings].append(time)
self.output_size[file_settings] = size
def get_results(self, file_settings):
return self.data[file_settings]
def pretty_print(self):
print(f"------------------------------------------------------")
print(f"{self.filename}")
for file_settings, values in self.data.items():
print(f" {file_settings.configuration_string()}")
print(f" {values}")
print(f" Output file size: {self.output_size[file_settings]}")
print(" ---------------------------------------------------")
class Benchmark(ABC):
@abstractmethod
def __init__(self,
filepaths: List[str],
file_settings: List[FileSettings],
result_directory,
measurements_per_file=10):
self.file_settings = file_settings
self.file_settings_original_order = file_settings.copy()
self.measurements_per_file = measurements_per_file
self.measurements = [Measurement(filepath, self.file_settings) for filepath in filepaths]
self.result_directory = os.path.normpath(result_directory)
def measure_for_file_settings(self, measurement, image):
# Shuffle to avoid benefits/downsides because of test order
random.shuffle(self.file_settings)
for file_setting in self.file_settings:
file_setting.activate()
output_filename = f"test{file_setting.extension}"
output_path = os.path.join(bpy.app.tempdir, output_filename)
for i in range(self.measurements_per_file):
start = time.time()
try:
image.save_render(output_path)
except:
print(f"Could not save image {output_filename}")
end = time.time()
time_passed = end - start
file_size = os.path.getsize(output_path)
measurement.add_result(file_setting, time_passed, file_size)
measurement.image_resolution = (image.size[0], image.size[1])
@abstractmethod
def run(self):
pass
def save_results(self):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")
try:
filename_time = f"{timestamp}_benchmark.svg"
filename_time_min = f"{timestamp}_benchmark_min.svg"
filename_size = f"{timestamp}_benchmark_size.svg"
result_file_time = os.path.join(self.result_directory, filename_time)
result_file_time_min = os.path.join(self.result_directory, filename_time_min)
result_file_size = os.path.join(self.result_directory, filename_size)
plot = Plot()
plot.time(result_file_time, self.measurements, self.file_settings_original_order)
plot.time_min(result_file_time_min, self.measurements, self.file_settings_original_order)
plot.file_size(result_file_size, self.measurements, self.file_settings_original_order)
except (ImportError, ModuleNotFoundError) as e:
print("Could not import matplotlib.")
filename = f"{timestamp}_benchmark.txt"
result_file = os.path.join(self.result_directory, filename)
with open(result_file, 'w') as f:
with redirect_stdout(f):
for measurement in self.measurements:
measurement.pretty_print()
class BenchmarkProject(Benchmark):
def __init__(self,
file_settings: List[FileSettings],
result_directory,
measurements_per_file=10):
super().__init__([bpy.data.filepath], file_settings, result_directory, measurements_per_file)
def measure_for_file_settings_render(self):
image = bpy.data.images["Render Result"]
self.measure_for_file_settings(self.measurements[0], image)
self.save_results()
def run(self):
bpy.ops.render.render()
self.measure_for_file_settings_render()
class BenchmarkImage(Benchmark):
def __init__(self,
filepaths: List[str],
file_settings: List[FileSettings],
result_directory,
measurements_per_file=10):
super().__init__(filepaths, file_settings, result_directory, measurements_per_file)
def run(self):
for measurement in self.measurements:
self.current_measurement = measurement
image = bpy.data.images.load(measurement.filepath, check_existing=False)
self.current_image = image
self.measure_for_file_settings(measurement, image)
image.user_clear()
bpy.data.images.remove(image)
self.save_results()
class Plot():
def time(self, filepath: str, measurements: List[Measurement], file_settings: List[FileSettings]):
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
mean_values = {}
stdev_values = {}
x_labels = []
for measurement in measurements:
res_x = measurement.image_resolution[0]
res_y = measurement.image_resolution[1]
x_labels.append(
f"{measurement.filename}\n({res_x}x{res_y})")
for file_setting in file_settings:
result = measurement.get_results(file_setting)
mean = statistics.mean(result)
stdev = statistics.stdev(result)
if mean_values.get(file_setting) is None:
mean_values[file_setting] = []
mean_values[file_setting].append(mean)
if stdev_values.get(file_setting) is None:
stdev_values[file_setting] = []
stdev_values[file_setting].append(stdev)
fig, ax = plt.subplots()
fig.set_size_inches(18.5, 10.5)
start = 1.0
step = 10.0
ind = np.linspace(start=start, stop=len(x_labels) * step,
num=len(x_labels))
group_width = step
width = group_width / len(mean_values)
bars = []
bars_legend = []
counter = 0.0
for file_setting, values in mean_values.items():
stdev = stdev_values.get(file_setting)
loc_x = np.linspace(start=start - group_width / 2.0 + width * counter,
stop=len(x_labels) * step - group_width / 2.0 + width * counter,
num=len(values)) # num steps of values of particular format
bars.append(ax.bar(loc_x, values, width, bottom=0, yerr=stdev))
bars_legend.append(
f"{file_setting.label}")
## Use for detailed configuration output in the legend
# f"{file_setting.configuration_string()}")
counter += 1.0
ax.set_title('Benchmark Saving Times')
ax.set_xticks(ind - width / 2)
ax.set_xticklabels(x_labels)
ax.set_ylabel('Average saving time in seconds')
ax.set_xlabel('File')
ax.legend(bars, bars_legend, loc='upper left', bbox_to_anchor=(1.0, 1.01))
ax.autoscale_view()
plt.grid(True)
plt.savefig(filepath, bbox_inches='tight', dpi=300)
def time_min(self, filepath: str, measurements: List[Measurement], file_settings: List[FileSettings]):
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
min_values = {}
x_labels = []
for measurement in measurements:
res_x = measurement.image_resolution[0]
res_y = measurement.image_resolution[1]
x_labels.append(
f"{measurement.filename}\n({res_x}x{res_y})")
for file_setting in file_settings:
result = measurement.get_results(file_setting)
minimum = min(result)
if min_values.get(file_setting) is None:
min_values[file_setting] = []
min_values[file_setting].append(minimum)
fig, ax = plt.subplots()
fig.set_size_inches(18.5, 10.5)
start = 1.0
step = 10.0
ind = np.linspace(start=start, stop=len(x_labels) * step,
num=len(x_labels))
group_width = step
width = group_width / len(min_values)
bars = []
bars_legend = []
counter = 0.0
for file_setting, values in min_values.items():
loc_x = np.linspace(start=start - group_width / 2.0 + width * counter,
stop=len(x_labels) * step - group_width / 2.0 + width * counter,
num=len(values)) # num steps of values of particular format
bars.append(ax.bar(loc_x, values, width, bottom=0))
bars_legend.append(
f"{file_setting.label}")
## Use for detailed configuration output in the legend
# f"{file_setting.configuration_string()}")
counter += 1.0
ax.set_title('Benchmark Saving Times')
ax.set_xticks(ind - width / 2)
ax.set_xticklabels(x_labels)
ax.set_ylabel('Minimum saving time in seconds')
ax.set_xlabel('File')
ax.legend(bars, bars_legend, loc='upper left', bbox_to_anchor=(1.0, 1.01))
ax.autoscale_view()
plt.grid(True)
plt.savefig(filepath, bbox_inches='tight', dpi=300)
def file_size(self, filepath: str, measurements: List[Measurement], file_settings: List[FileSettings]):
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
values = {}
x_labels = []
for measurement in measurements:
res_x = measurement.image_resolution[0]
res_y = measurement.image_resolution[1]
x_labels.append(
f"{measurement.filename}\n({res_x}x{res_y})")
for file_setting in file_settings:
if values.get(file_setting) is None:
values[file_setting] = []
value = measurement.output_size[file_setting]
value *= 0.000001 # Convert to MB
values[file_setting].append(value)
fig, ax = plt.subplots()
fig.set_size_inches(18.5, 10.5)
start = 1.0
step = 10.0
ind = np.linspace(start=start, stop=len(x_labels) * step,
num=len(x_labels))
group_width = step
width = group_width / len(values)
bars = []
bars_legend = []
counter = 0.0
for file_setting, value_entry in values.items():
loc_x = np.linspace(start=start - group_width / 2.0 + width * counter,
stop=len(x_labels) * step - group_width / 2.0 + width * counter,
num=len(value_entry)) # num steps of values of particular format
bars.append(ax.bar(loc_x, value_entry, width, bottom=0))
bars_legend.append(
f"{file_setting.label}")
## Use for detailed configuration output in the legend
# f"{file_setting.configuration_string()}")
counter += 1.0
ax.set_title('Benchmark File Size')
ax.set_xticks(ind - width / 2)
ax.set_xticklabels(x_labels)
ax.set_ylabel('File size in MB')
ax.set_xlabel('File')
ax.legend(bars, bars_legend, loc='upper left', bbox_to_anchor=(1.0, 1.01))
ax.autoscale_view()
plt.grid(True)
plt.savefig(filepath, bbox_inches='tight', dpi=300)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment