feat: Use `canvas.draw()` for plotting.
The performance difference isn't as clear cut as hoped. However, the plotting procedure is enormously more straightforward, and performance is more predictable. So it's worth it. We're managing to perfectly reuse figure/canvas/axis, but still hovering at around 70-80ms. Mind you, the tested machine is an older laptop. Still, things feel interactive enough, especially together with the other modifications. To really amp it up, we can look into blitting. It requires alterations to the plotting methodology, but it offers a cached approach to drawing only altered pixels (the bottleneck with `canvas.draw()` is that it needs to render all the pixels, every time). We can also try to lower the resolution if it's too slow.main
parent
c63dda2224
commit
4e1eb19a88
|
@ -1,7 +1,6 @@
|
||||||
"""Declares `ManagedBLImage`."""
|
"""Declares `ManagedBLImage`."""
|
||||||
|
|
||||||
import io
|
#import time
|
||||||
import time
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
@ -238,60 +237,60 @@ class ManagedBLImage(base.ManagedObj):
|
||||||
dpi: int | None = None,
|
dpi: int | None = None,
|
||||||
bl_select: bool = False,
|
bl_select: bool = False,
|
||||||
):
|
):
|
||||||
times = [time.perf_counter()]
|
# times = [time.perf_counter()]
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
times.append(time.perf_counter() - times[0])
|
|
||||||
# Compute Plot Dimensions
|
# Compute Plot Dimensions
|
||||||
aspect_ratio, _dpi, _width_inches, _height_inches, width_px, height_px = (
|
aspect_ratio, _dpi, _width_inches, _height_inches, width_px, height_px = (
|
||||||
self.gen_image_geometry(width_inches, height_inches, dpi)
|
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
|
# Create MPL Figure, Axes, and Compute Figure Geometry
|
||||||
fig, ax = plt.subplots(
|
fig, canvas, ax = image_ops.mpl_fig_canvas_ax(
|
||||||
figsize=[_width_inches, _height_inches],
|
_width_inches, _height_inches, _dpi
|
||||||
dpi=_dpi,
|
|
||||||
)
|
)
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['MPL Fig Canvas Axis', time.perf_counter() - times[0]])
|
||||||
ax.set_aspect(aspect_ratio)
|
|
||||||
times.append(time.perf_counter() - times[0])
|
ax.clear()
|
||||||
cmp_width_px, cmp_height_px = fig.canvas.get_width_height()
|
# times.append(['Clear Axis', time.perf_counter() - times[0]])
|
||||||
times.append(time.perf_counter() - times[0])
|
|
||||||
ax.set_aspect('auto') ## Workaround aspect-ratio bugs
|
|
||||||
times.append(time.perf_counter() - times[0])
|
|
||||||
|
|
||||||
# Plot w/User Parameter
|
# Plot w/User Parameter
|
||||||
func_plotter(ax)
|
func_plotter(ax)
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['Plot!', time.perf_counter() - times[0]])
|
||||||
|
|
||||||
# Save Figure to BytesIO
|
# Save Figure to BytesIO
|
||||||
with io.BytesIO() as buff:
|
canvas.draw()
|
||||||
fig.savefig(buff, format='raw', dpi=dpi)
|
# times.append(['Draw Pixels', time.perf_counter() - times[0]])
|
||||||
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])
|
|
||||||
|
|
||||||
image_data = np.flipud(image_data).astype(np.float32) / 255
|
canvas_width_px, canvas_height_px = fig.canvas.get_width_height()
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['Get Canvas Dims', time.perf_counter() - times[0]])
|
||||||
plt.close(fig)
|
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
|
# Optimized Write to Blender Image
|
||||||
bl_image = self.bl_image(cmp_width_px, cmp_height_px, 'RGBA', 'uint8')
|
bl_image = self.bl_image(canvas_width_px, canvas_height_px, 'RGBA', 'uint8')
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['Get BLImage', time.perf_counter() - times[0]])
|
||||||
bl_image.pixels.foreach_set(image_data.ravel())
|
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()
|
bl_image.update()
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['Update BLImage', time.perf_counter() - times[0]])
|
||||||
|
|
||||||
if bl_select:
|
if bl_select:
|
||||||
self.bl_select()
|
self.bl_select()
|
||||||
times.append(time.perf_counter() - times[0])
|
# times.append(['Select BLImage', time.perf_counter() - times[0]])
|
||||||
# log.critical('Timing of MPL Plot: %s', str(times))
|
|
||||||
|
# log.critical('Timing of MPL Plot')
|
||||||
|
# for timing in times:
|
||||||
|
# log.critical(timing)
|
||||||
|
|
||||||
|
|
||||||
@bpy.app.handlers.persistent
|
@bpy.app.handlers.persistent
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Useful image processing operations for use in the addon."""
|
"""Useful image processing operations for use in the addon."""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
import functools
|
||||||
import time
|
import time
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
@ -9,10 +10,15 @@ import jax.numpy as jnp
|
||||||
import jaxtyping as jtyp
|
import jaxtyping as jtyp
|
||||||
import matplotlib
|
import matplotlib
|
||||||
import matplotlib.axis as mpl_ax
|
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 import contracts as ct
|
||||||
from blender_maxwell.utils import logger
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
mplstyle.use('fast') ## TODO: Does this do anything?
|
||||||
|
|
||||||
log = logger.get(__name__)
|
log = logger.get(__name__)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -110,6 +116,20 @@ def rgba_image_from_2d_map(
|
||||||
return rgba_image_from_2d_map__grayscale(map_2d)
|
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
|
# - Plotters
|
||||||
####################
|
####################
|
||||||
|
@ -230,7 +250,7 @@ def plot_heatmap_2d(
|
||||||
y_unit = info.dim_units[y_name]
|
y_unit = info.dim_units[y_name]
|
||||||
|
|
||||||
heatmap = ax.imshow(data, aspect='auto', interpolation='none')
|
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_title('Heatmap')
|
||||||
ax.set_xlabel(f'{x_name}' + (f'({x_unit})' if x_unit is not None else ''))
|
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 ''))
|
ax.set_ylabel(f'{y_name}' + (f'({y_unit})' if y_unit is not None else ''))
|
||||||
|
|
Loading…
Reference in New Issue