Source code for magmap.gui.image_viewer
"""Image viewer support"""
from typing import List, Optional, Sequence, TYPE_CHECKING
from magmap.settings import config
if TYPE_CHECKING:
from matplotlib import artist, backend_bases, figure
_logger = config.logger.getChild(__name__)
# inspired by Matplotlib example:
# https://matplotlib.org/stable/tutorials/advanced/blitting.html#class-based-example
[docs]
class Blitter:
"""Controller for blitting in Matplotlib graphics.
Improves interactive graphics performance by reducing repetitive drawing.
"""
def __init__(self, fig, artists=None):
"""Initialized the blit controller."""
#: Matplotlib figure.
self.fig: "figure.Figure" = fig
#: Internal representation of tracked artists.
self._artists = []
self.artists = artists
#: Canvas background.
self._bkgd = None
#: Event listener IDs.
self._listeners: List[int] = [
fig.canvas.mpl_connect("draw_event", self.on_draw)
]
@property
def artists(self) -> List["artist.Artist"]:
"""Tracked artists for blitting."""
return self._artists
@artists.setter
def artists(self, vals: Optional[Sequence["artist.Artist"]]):
"""Set tracked artists.
Args:
vals: Artists to add. Can be None, which will reset artists to an
empty list.
"""
if vals is None:
# reset artists to empty list
self._artists = []
return
for val in vals:
# add artist
self.add_artist(val)
[docs]
def add_artist(self, arist: "artist.Artist", i: Optional[int] = None):
"""Add tracked artist.
Args:
arist: Artist to track. Only artists in :attr:`fig` will be added.
i: Index at which to add the artist. Defaults to None, which will
append the artist.
"""
if arist.figure != self.fig:
# skip artists in other figs
_logger.warn(
f"Artist from different figure added for blitting: {arist}")
return
# flag as animated for update, which appears to prevent updating
# non-animated artists as well
arist.set_animated(True)
if i is None:
# append artist
self._artists.append(arist)
else:
# use lower index to position behind other animated artists
self._artists.insert(i, arist)
[docs]
def on_draw(self, evt: Optional["backend_bases.DrawEvent"]):
"""Recapture backgrouna and draw the figure canvas."""
canvas = self.fig.canvas
if evt and evt.canvas != canvas:
# skip drawing if event is from another canvas
return
# recapture the canvas background and draw artists
self._bkgd = canvas.copy_from_bbox(self.fig.bbox)
self._draw_artists()
def _draw_artists(self):
"""Draw arists."""
for art in self.artists:
self.fig.draw_artist(art)
[docs]
def update(self):
"""Update the canvas."""
canvas = self.fig.canvas
if self._bkgd is None:
# get background if not yet set
self.on_draw(None)
else:
# restore saved background, draw artists, and copy to GUI state
canvas.restore_region(self._bkgd)
self._draw_artists()
canvas.blit(self.fig.bbox)
# flush GUI events and repaint if necessary
canvas.flush_events()