diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py index b97eced..33f5d76 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py @@ -1,7 +1,6 @@ """Declares `ManagedBLImage`.""" -import io -import time +#import time import typing as typ import bpy @@ -238,60 +237,60 @@ class ManagedBLImage(base.ManagedObj): dpi: int | None = None, bl_select: bool = False, ): - times = [time.perf_counter()] - import matplotlib.pyplot as plt + # times = [time.perf_counter()] - times.append(time.perf_counter() - times[0]) # Compute Plot Dimensions aspect_ratio, _dpi, _width_inches, _height_inches, width_px, height_px = ( self.gen_image_geometry(width_inches, height_inches, dpi) ) - times.append(time.perf_counter() - times[0]) + # times.append(['Image Geometry', time.perf_counter() - times[0]]) # Create MPL Figure, Axes, and Compute Figure Geometry - fig, ax = plt.subplots( - figsize=[_width_inches, _height_inches], - dpi=_dpi, + fig, canvas, ax = image_ops.mpl_fig_canvas_ax( + _width_inches, _height_inches, _dpi ) - times.append(time.perf_counter() - times[0]) - ax.set_aspect(aspect_ratio) - times.append(time.perf_counter() - times[0]) - cmp_width_px, cmp_height_px = fig.canvas.get_width_height() - times.append(time.perf_counter() - times[0]) - ax.set_aspect('auto') ## Workaround aspect-ratio bugs - times.append(time.perf_counter() - times[0]) + # times.append(['MPL Fig Canvas Axis', time.perf_counter() - times[0]]) + + ax.clear() + # times.append(['Clear Axis', time.perf_counter() - times[0]]) # Plot w/User Parameter func_plotter(ax) - times.append(time.perf_counter() - times[0]) + # times.append(['Plot!', time.perf_counter() - times[0]]) # Save Figure to BytesIO - with io.BytesIO() as buff: - fig.savefig(buff, format='raw', dpi=dpi) - times.append(time.perf_counter() - times[0]) - buff.seek(0) - image_data = np.frombuffer( - buff.getvalue(), - dtype=np.uint8, - ).reshape([cmp_height_px, cmp_width_px, -1]) - times.append(time.perf_counter() - times[0]) + canvas.draw() + # times.append(['Draw Pixels', time.perf_counter() - times[0]]) - image_data = np.flipud(image_data).astype(np.float32) / 255 - times.append(time.perf_counter() - times[0]) - plt.close(fig) + canvas_width_px, canvas_height_px = fig.canvas.get_width_height() + # times.append(['Get Canvas Dims', time.perf_counter() - times[0]]) + image_data = ( + np.float32( + np.flipud( + np.frombuffer(fig.canvas.buffer_rgba(), dtype=np.uint8).reshape( + fig.canvas.get_width_height()[::-1] + (4,) + ) + ) + ) + / 255 + ) + # times.append(['Load Data from Canvas', time.perf_counter() - times[0]]) # Optimized Write to Blender Image - bl_image = self.bl_image(cmp_width_px, cmp_height_px, 'RGBA', 'uint8') - times.append(time.perf_counter() - times[0]) + bl_image = self.bl_image(canvas_width_px, canvas_height_px, 'RGBA', 'uint8') + # times.append(['Get BLImage', time.perf_counter() - times[0]]) bl_image.pixels.foreach_set(image_data.ravel()) - times.append(time.perf_counter() - times[0]) + # times.append(['Set Pixels', time.perf_counter() - times[0]]) bl_image.update() - times.append(time.perf_counter() - times[0]) + # times.append(['Update BLImage', time.perf_counter() - times[0]]) if bl_select: self.bl_select() - times.append(time.perf_counter() - times[0]) - # log.critical('Timing of MPL Plot: %s', str(times)) + # times.append(['Select BLImage', time.perf_counter() - times[0]]) + + # log.critical('Timing of MPL Plot') + # for timing in times: + # log.critical(timing) @bpy.app.handlers.persistent diff --git a/src/blender_maxwell/utils/image_ops.py b/src/blender_maxwell/utils/image_ops.py index 1fdf328..2d7e32d 100644 --- a/src/blender_maxwell/utils/image_ops.py +++ b/src/blender_maxwell/utils/image_ops.py @@ -1,6 +1,7 @@ """Useful image processing operations for use in the addon.""" import enum +import functools import time import typing as typ @@ -9,10 +10,15 @@ import jax.numpy as jnp import jaxtyping as jtyp import matplotlib import matplotlib.axis as mpl_ax +import matplotlib.backends.backend_agg +import matplotlib.figure +import matplotlib.style as mplstyle from blender_maxwell import contracts as ct from blender_maxwell.utils import logger +mplstyle.use('fast') ## TODO: Does this do anything? + log = logger.get(__name__) #################### @@ -110,6 +116,20 @@ def rgba_image_from_2d_map( return rgba_image_from_2d_map__grayscale(map_2d) +#################### +# - MPL Helpers +#################### +@functools.lru_cache(maxsize=16) +def mpl_fig_canvas_ax(width_inches: float, height_inches: float, dpi: int): + fig = matplotlib.figure.Figure(figsize=[width_inches, height_inches], dpi=dpi) + canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig) + ax = fig.add_subplot() + + # The Customer is Always Right (in Matters of Taste) + #fig.tight_layout(pad=0) + return (fig, canvas, ax) + + #################### # - Plotters #################### @@ -230,7 +250,7 @@ def plot_heatmap_2d( y_unit = info.dim_units[y_name] heatmap = ax.imshow(data, aspect='auto', interpolation='none') - ax.figure.colorbar(heatmap, ax=ax) + #ax.figure.colorbar(heatmap, ax=ax) ax.set_title('Heatmap') ax.set_xlabel(f'{x_name}' + (f'({x_unit})' if x_unit is not None else '')) ax.set_ylabel(f'{y_name}' + (f'({y_unit})' if y_unit is not None else ''))