Module vipy.gui.using_matplotlib

Expand source code Browse git
import os
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.patches import Circle
from matplotlib.collections import PatchCollection
import matplotlib.colors as mcolors
import numpy as np
from vipy.util import islist, temppng
import sys
from vipy.globals import log
from vipy.version import Version
import numpy as np


FIGHANDLE = {}
matplotlib.rcParams['toolbar'] = 'None'
matplotlib_version_at_least_3p3 = Version.from_string(matplotlib.__version__) >= '3.3'


PRIMARY_COLORLIST = ['green','blue','red','cyan','orange','yellow','violet','white'] + ['tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan']
COLORLIST = PRIMARY_COLORLIST + [str(name) for (name, hex) in mcolors.cnames.items() if str(name) not in PRIMARY_COLORLIST]  # use primary colors first
COLORLIST_LUMINANCE = [(np.array(mcolors.to_rgb(name)) * np.array([0.2126, 0.7152, 0.0722])).sum() for name in COLORLIST]
DARK_COLORLIST = tuple(c for (c,l) in zip(COLORLIST, COLORLIST_LUMINANCE) if l>=0.5)  # colors that show well on dark backgrounds
LIGHT_COLORLIST = tuple(c for (c,l) in zip(COLORLIST, COLORLIST_LUMINANCE) if l<=0.5) # colors that show well on light backgrounds


# Optional latex strings in captions
try:
    import vipy.globals
    if vipy.globals.GLOBAL['LATEX'] is not None:
        from distutils.spawn import find_executable
        if not find_executable('latex'):
            raise
        matplotlib.rc('text', usetex=True)  # requires latex installed
except:
    pass  # ignored if latex is not installed or not wanted


def escape_to_exit(event):
    if event.key == 'escape' or event.key == 'q' or event.key == 'ctrl+c':
        import vipy.globals            
        vipy.globals._user_hit_escape(True)
    
def flush():
    if matplotlib.get_backend().lower() == 'macosx':
        plt.draw()  # YUCK: to flush buffer on video play, will be slow
    plt.pause(0.001)

    
def imflush():
    if matplotlib.get_backend().lower() == 'macosx':
        plt.draw()  # YUCK: to flush buffer on video play, will be slow
    plt.pause(0.001)

    # this is necessary for imshow only, maybe to trigger remove() of child?
    # However, this breaks jupyter notebook image show if we use gca()
    [a.annotate('', (0,0)) for i in plt.get_fignums() for a in plt.figure(i).axes]
    
    #plt.gca().annotate('', (0,0))  

    
def show(fignum):
    fig = plt.figure(fignum) 
    fig.canvas.draw()    
    plt.ion()
    plt.show()

    
def noshow(fignum):
    plt.ioff()


def savefig(filename=None, fignum=None, pad_inches=0, dpi=None, bbox_inches='tight', format=None):
    if fignum is not None:
        plt.figure(fignum)
    if filename is None:
        filename = temppng()
    plt.savefig(filename, pad_inches=pad_inches, dpi=dpi, bbox_inches=bbox_inches, format=format)
    return filename


def figure(fignum=None):
    if fignum is not None:
        plt.figure(fignum)
    else:
        plt.figure()
    return plt


def close(fignum):
    global FIGHANDLE
    if fignum in FIGHANDLE:
        plt.close(fignum)
        FIGHANDLE.pop(fignum, None)
        return None
    else:
        return None


def closeall():
    global FIGHANDLE
    FIGHANDLE = {}
    return plt.close('all')


def _imshow_tight(img, fignum=None, keypress=True):
    """Helper function to show an image in a figure window"""
    dpi = 100.0
    fig = plt.figure(fignum, dpi=dpi, figsize=(img.shape[1] / dpi, img.shape[0] / dpi)) if not plt.fignum_exists(fignum) else plt.figure(fignum)
    plt.clf()

    # Tight axes
    ax = plt.Axes(fig, [0., 0., 1.0, 1.0], frameon=False)
    fig.add_axes(ax)
    for a in plt.gcf().axes:
        a.get_xaxis().set_visible(False)
        a.get_yaxis().set_visible(False)
    imh = plt.imshow(img, animated=True, interpolation='nearest', aspect='equal')

    if keypress:
        fig.canvas.mpl_connect('key_press_event', escape_to_exit)
        import vipy.globals    
        vipy.globals._user_hit_escape(False)

    return (fig.number, imh)


def imshow(img, fignum=None):
    """Show an image in a figure window (optionally visible), reuse previous figure if it is the same shape"""
    global FIGHANDLE

    if fignum in plt.get_fignums() and fignum in FIGHANDLE and FIGHANDLE[fignum].get_size() == img.shape[0:2]:
        # Do not delete and recreate the figure, just change the pixels 
        FIGHANDLE[fignum].set_data(img)

        # Delete all polygon and text overlays from previous drawing so that they can be overwritten on current frame
        for c in plt.gca().get_children():
            desc = c.__repr__()
            if 'Text' in desc or 'Polygon' in desc or 'Circle' in desc or 'Line' in desc or 'Patch' in desc or 'PathCollection' in desc:
                try:
                    c.remove()
                except:
                    # MacOSX fails with the error: NotImplementedError: cannot remove artist
                    # fallback on closing figure completely
                    (fignum, imh) = _imshow_tight(img, fignum=fignum) 
                    FIGHANDLE[fignum] = imh
                    return fignum

    else:
        # Jupyter notebook does not respect fignum.  It is always one when inspecting plt.get_fignums()
        if fignum in plt.get_fignums() and fignum in FIGHANDLE:
            close(fignum)
            #pass  # don't close unless user requests, this is faster, but window size does not change with image resolution
        (fignum, imh) = _imshow_tight(img, fignum=fignum)
        FIGHANDLE[fignum] = imh
    return fignum


def text(caption, xmin, ymin, fignum=None, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, linewidth=3, facecolor='white', facealpha=0.5, alpha=1.0, pad=0.5, captionoffset=0):
    plt.figure(fignum) if fignum is not None else plt.gcf()
    lw = linewidth  # pull in the boxes by linewidth so that they do not overhang the figure

    newlines = caption.count('\n')
    
    # MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3            
    handle = plt.annotate(**{'text' if matplotlib_version_at_least_3p3 else 's': caption},
                          alpha=alpha, xy=(xmin,ymin), xytext=(xmin, ymin+captionoffset), xycoords='data',
                          color=textcolor, bbox=None if textfacecolor is None else dict(facecolor=textfacecolor, edgecolor=None, alpha=textfacealpha, boxstyle='square', pad=pad),
                          fontsize=fontsize, clip_on=True)

    return fignum

def boundingbox(img, xmin, ymin, xmax, ymax, bboxcaption=None, fignum=None, bboxcolor='green', facecolor='white', facealpha=0.5, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0), linewidth=3):
    """Draw a captioned bounding box on a previously shown image"""
    plt.figure(fignum)
    ax = plt.gca()
    lw = linewidth  # pull in the boxes by linewidth so that they do not overhang the figure
    (H,W) = (img.shape[0], img.shape[1])
    
    ymin_frac = 1.0 - (min(ymax, H - lw/2) / float(H))
    ymax_frac = 1.0 - (max(ymin, lw/2) / float(H))
    
    y0, y1 = ax.get_ylim()                 # data‐coordinates of bottom and top of the axes
    rect = Rectangle((xmin, ymin), xmax - xmin,  (ymax_frac - ymin_frac) * abs(y1 - y0), 
                 linewidth=lw,
                 edgecolor=(*mcolors.to_rgb(bboxcolor),0.4),
                 facecolor=(*mcolors.to_rgb(facecolor),facealpha),
                 #alpha=facealpha,
                 transform=ax.transData, capstyle='round', joinstyle='bevel', clip_on=True)
    ax.add_patch(rect)

    # Text string
    if bboxcaption is not None:
        # a) Compute x‐fraction: (xmin − x0)/(x1 − x0)
        x0, x1 = ax.get_xlim()
        xmin_frac = ((xmin - x0) / (x1 - x0)) #+ (captionoffset[0]/H)
        
        # b) y1_frac is already the top of the box in axes‐fraction; add vertical offset
        ymin_frac_text = ymin_frac #+ (captionoffset[1]/W)
        ymax_frac = 1.0 - ((max(ymin, lw/2)) / float(H)) 
        
        newlines = bboxcaption.count('\n')

        captionoffset = captionoffset if ymin > 20 else (captionoffset[0], captionoffset[1]+(20*(1)))  # move down a bit if near top of image, shift once per newline in caption

        # MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3   
        plt.annotate(**{'text' if matplotlib_version_at_least_3p3 else 's': bboxcaption},
                     xy=(xmin_frac, ymax_frac),
                     xytext=(captionoffset[0], -captionoffset[1]),
                     textcoords='offset pixels',
                     xycoords='axes fraction',
                     color=textcolor,
                     fontsize=fontsize,
                     bbox=dict(
                         facecolor=textfacecolor,
                         edgecolor=textcolor,
                         alpha=textfacealpha,
                         boxstyle='square',
                     ),
                     horizontalalignment='left',
                     verticalalignment='top',
                     clip_on=True
                     )

    return fignum


def imdetection(img, detlist, fignum=None, bboxcolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0)):
    """Show bounding boxes from a list of vipy.object.Detections on the same image, plotted in list order with optional captions """

    # Create image
    #fignum = imshow(img, fignum=fignum)  # this can fail on MacOSX, go the slow route instead
    fignum = _imshow_tight(img, fignum=fignum)     

    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html
    
    # Valid detections
    for (k,det) in enumerate(detlist):
        if do_caption and det.category() is not None and len(det.category()) != 0 and (det.category()[0:2] != '__'):  # prepending category with '__' will disable caption
            bboxcaption = det.category()
        else:
            bboxcaption = None

        if islist(bboxcolor):
            bboxcolor_ = bboxcolor[k]
        else:
            bboxcolor_ = bboxcolor

        if islist(textcolor):
            textcolor_ = textcolor[k]
        else:
            textcolor_ = textcolor

        boundingbox(img, xmin=det.xmin(), ymin=det.ymin(), xmax=det.xmax(), ymax=det.ymax(), bboxcaption=bboxcaption,
                    fignum=fignum, bboxcolor=bboxcolor_, facecolor=facecolor, facealpha=facealpha, textcolor=textcolor_, textfacecolor=textfacecolor, fontsize=fontsize, captionoffset=captionoffset, textfacealpha=textfacealpha)

    return fignum


def imkeypoints(img, kplist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0)):
    """Show `vipy.object.Keypoint2d` on the same image, plotted in list order with optional captions """

    # Create image
    fignum = imshow(img, fignum=fignum) 

    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html
    
    # Valid keypoints
    (x,y,size,color,caption,captioncolor) = ([],[],[],[],[],[])
    for (k,p) in enumerate(kplist):
        x.append(p.x)
        y.append(p.y)
        size.append(max(p.r, 1)**2)  # size is pts squared
        
        if do_caption and p.category() is not None and len(p.category()) != 0 and (p.category()[0:2] != '__'):  # prepending category with '__' will disable caption
            caption.append(p.category())
        else:
            caption.append(None)

        if islist(bordercolor):
            color.append(mcolors.to_hex(bordercolor[k]) + 'BB')  # with alpha
        else:
            color.append(mcolors.to_hex(bordercolor) + 'BB')  # with alpha
            
        if islist(textcolor):
            captioncolor.append(textcolor[k])
        else:
            captioncolor.append(textcolor)

    plt.figure(fignum)
    plt.scatter(x, y, c=color, s=size)
    return fignum


def imobjects(img, objlist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0), kp_alpha=0.8):
    """Show `vipy.object.Keypoint2d` on the same image, plotted in list order with optional captions """

    # Create image
    fignum = imshow(img, fignum=fignum) 
    plt.gca().set_autoscale_on(False)


    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html

    # Valid detections
    detlist = [p for p in objlist if isinstance(p, vipy.object.Detection)]
    if len(detlist) > 0:
        for (k,det) in enumerate(detlist):
            if do_caption and det.category() is not None and len(det.category()) != 0 and (det.category()[0:2] != '__'):  # prepending category with '__' will disable caption
                bboxcaption = det.category()
            else:
                bboxcaption = None

            if islist(bordercolor):
                bboxcolor_ = bordercolor[k]
            else:
                bboxcolor_ = bordercolor

            if islist(textcolor):
                textcolor_ = textcolor[k]
            else:
                textcolor_ = textcolor

            boundingbox(img, xmin=det.xmin(), ymin=det.ymin(), xmax=det.xmax(), ymax=det.ymax(), bboxcaption=bboxcaption,
                        fignum=fignum, bboxcolor=bboxcolor_, facecolor=bboxcolor_, facealpha=facealpha, textcolor=textcolor_, textfacecolor=textfacecolor, fontsize=fontsize, captionoffset=captionoffset, textfacealpha=textfacealpha)
    
    # Valid keypoints
    kplist = [p for p in objlist if isinstance(p, vipy.object.Keypoint2d)]    
    plt.gca().set_autoscale_on(False)

    if len(kplist) > 0:
        (x,y,size,colors,r,patches) = ([],[],[],[],[],[])
        for (k,p) in enumerate(kplist):
            x.append(p.x)
            y.append(p.y)
            r.append(p.r)
            
            c = mcolors.to_hex(bordercolor[k] if islist(bordercolor) else bordercolor)
            colors.append(c + format(int(255*kp_alpha), '02X'))  # with alpha
            patches.append(Circle((p.x, p.y), p.r))

        plt.gca().add_collection(PatchCollection(patches, facecolors=colors, edgecolors='silver', clip_on=True, linewidths=0.5))  # scale radius with window size
    return fignum


def imframe(img, fr, color='b', markersize=10, label=None, figure=None):
    """Show a scatterplot of fr=[[x1,y1],[x2,y2]...] 2D points overlayed on an image, all the same color"""
    if figure is not None:
        fig = plt.figure(figure)
    else:
        fig = plt.figure()

    figure = plt.gcf().number

    plt.clf()

    ax = plt.Axes(fig, [0., 0., 1., 1.], frameon=False)
    fig.add_axes(ax)

    plt.axis('off')
    ax.set_axis_off()
    for a in plt.gcf().axes:
        a.get_xaxis().set_visible(False)
        a.get_yaxis().set_visible(False)

    plt.autoscale(tight=True)
    plt.plot(fr[:,0],fr[:,1],'%s.' % color, markersize=markersize, axes=ax)

    if label is not None:
        for ((x,y),lbl) in zip(fr, label):
            ax.text(x, y, lbl, color='white')

    return plt


def frame(fr, im=None, color='b.', markersize=10, figure=None, caption=None):
    """Show a scatterplot of fr=[[x1,y1],[x2,y2]...] 2D points, all the same color"""
    if figure is not None:
        plt.figure(figure)
    else:
        plt.figure()
        plt.clf()

    plt.axes([0,0,1,1])
    plt.plot(fr[:,0],fr[:,1],color)
    plt.axis('off')
    plt.draw()


def colorlist(theme=None):
    """Return a list of named colors that are higher contrast with a white background if light_mode, else named colors that are higher contrast with a dark background if dark_mode"""        
    return DARK_COLORLIST if theme=='dark' else (LIGHT_COLORLIST if theme=='light' else COLORLIST)


def edit():
    import matplotlib.pyplot as plt 
    from matplotlib.widgets import Slider, Button, RadioButtons   
    button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
    ax2 = plt.axes([0.1, 0.325, 0.5, 0.05])

    ax2 = plt.axes([0, 0, 0.5, 0.05])
    ax2.patch.set_alpha(0.5) 
    Slider(ax2, 'Reset2', facecolor='red', alpha=0.5, valmin=0, valmax=10)
    ax2.patch.alpha = 0.5 

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

class Annotate(object):
    def __init__(self):
        self.ax = plt.gca()
        self.rect = Rectangle((0,0), 1, 1)
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.ax.add_patch(self.rect)
        self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
        self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)

    def on_press(self, event):
        print ('press')
        self.x0 = event.xdata
        self.y0 = event.ydata

    def on_release(self, event):
        print ('release')
        self.x1 = event.xdata
        self.y1 = event.ydata
        self.rect.set_width(self.x1 - self.x0)
        self.rect.set_height(self.y1 - self.y0)
        self.rect.set_xy((self.x0, self.y0))
        self.ax.figure.canvas.draw()


class DraggableRectangle(object):
    def __init__(self, rect):
        self.rect = rect
        self.press = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return

        contains, attrd = self.rect.contains(event)
        if not contains: return
        log.info('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if self.press is None: return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        #log.info('x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f' %
        #      (x0, xpress, event.xdata, dx, x0+dx))
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        self.rect.figure.canvas.draw()


    def on_release(self, event):
        'on release we reset the press data'
        self.press = None
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

        
    #a = Annotate()
    #plt.show()    

    #fig = plt.figure()
    #ax = fig.add_subplot(111)
    #rects = ax.bar(range(10), 20*np.random.rand(10))
    #drs = []
    #for rect in rects:
    #    dr = DraggableRectangle(rect)
    #    dr.connect()
    #    drs.append(dr)
    #
    #plt.show()


class DraggableRectangleFast(object):
    lock = None  # only one can be animated at a time
    def __init__(self, rect):
        self.rect = rect
        self.press = None
        self.background = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return
        if DraggableRectangle.lock is not None: return
        contains, attrd = self.rect.contains(event)
        if not contains: return
        log.info('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata
        DraggableRectangle.lock = self

        # draw everything but the selected rectangle and store the pixel buffer
        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        self.rect.set_animated(True)
        canvas.draw()
        self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

        # now redraw just the rectangle
        axes.draw_artist(self.rect)

        # and blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if DraggableRectangle.lock is not self:
            return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        # restore the background region
        canvas.restore_region(self.background)

        # redraw just the current rectangle
        axes.draw_artist(self.rect)

        # blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_release(self, event):
        'on release we reset the press data'
        if DraggableRectangle.lock is not self:
            return

        self.press = None
        DraggableRectangle.lock = None

        # turn off the rect animation property and reset the background
        self.rect.set_animated(False)
        self.background = None

        # redraw the full figure
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)    

Functions

def boundingbox(img, xmin, ymin, xmax, ymax, bboxcaption=None, fignum=None, bboxcolor='green', facecolor='white', facealpha=0.5, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0, 0), linewidth=3)

Draw a captioned bounding box on a previously shown image

Expand source code Browse git
def boundingbox(img, xmin, ymin, xmax, ymax, bboxcaption=None, fignum=None, bboxcolor='green', facecolor='white', facealpha=0.5, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0), linewidth=3):
    """Draw a captioned bounding box on a previously shown image"""
    plt.figure(fignum)
    ax = plt.gca()
    lw = linewidth  # pull in the boxes by linewidth so that they do not overhang the figure
    (H,W) = (img.shape[0], img.shape[1])
    
    ymin_frac = 1.0 - (min(ymax, H - lw/2) / float(H))
    ymax_frac = 1.0 - (max(ymin, lw/2) / float(H))
    
    y0, y1 = ax.get_ylim()                 # data‐coordinates of bottom and top of the axes
    rect = Rectangle((xmin, ymin), xmax - xmin,  (ymax_frac - ymin_frac) * abs(y1 - y0), 
                 linewidth=lw,
                 edgecolor=(*mcolors.to_rgb(bboxcolor),0.4),
                 facecolor=(*mcolors.to_rgb(facecolor),facealpha),
                 #alpha=facealpha,
                 transform=ax.transData, capstyle='round', joinstyle='bevel', clip_on=True)
    ax.add_patch(rect)

    # Text string
    if bboxcaption is not None:
        # a) Compute x‐fraction: (xmin − x0)/(x1 − x0)
        x0, x1 = ax.get_xlim()
        xmin_frac = ((xmin - x0) / (x1 - x0)) #+ (captionoffset[0]/H)
        
        # b) y1_frac is already the top of the box in axes‐fraction; add vertical offset
        ymin_frac_text = ymin_frac #+ (captionoffset[1]/W)
        ymax_frac = 1.0 - ((max(ymin, lw/2)) / float(H)) 
        
        newlines = bboxcaption.count('\n')

        captionoffset = captionoffset if ymin > 20 else (captionoffset[0], captionoffset[1]+(20*(1)))  # move down a bit if near top of image, shift once per newline in caption

        # MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3   
        plt.annotate(**{'text' if matplotlib_version_at_least_3p3 else 's': bboxcaption},
                     xy=(xmin_frac, ymax_frac),
                     xytext=(captionoffset[0], -captionoffset[1]),
                     textcoords='offset pixels',
                     xycoords='axes fraction',
                     color=textcolor,
                     fontsize=fontsize,
                     bbox=dict(
                         facecolor=textfacecolor,
                         edgecolor=textcolor,
                         alpha=textfacealpha,
                         boxstyle='square',
                     ),
                     horizontalalignment='left',
                     verticalalignment='top',
                     clip_on=True
                     )

    return fignum
def close(fignum)
Expand source code Browse git
def close(fignum):
    global FIGHANDLE
    if fignum in FIGHANDLE:
        plt.close(fignum)
        FIGHANDLE.pop(fignum, None)
        return None
    else:
        return None
def closeall()
Expand source code Browse git
def closeall():
    global FIGHANDLE
    FIGHANDLE = {}
    return plt.close('all')
def colorlist(theme=None)

Return a list of named colors that are higher contrast with a white background if light_mode, else named colors that are higher contrast with a dark background if dark_mode

Expand source code Browse git
def colorlist(theme=None):
    """Return a list of named colors that are higher contrast with a white background if light_mode, else named colors that are higher contrast with a dark background if dark_mode"""        
    return DARK_COLORLIST if theme=='dark' else (LIGHT_COLORLIST if theme=='light' else COLORLIST)
def edit()
Expand source code Browse git
def edit():
    import matplotlib.pyplot as plt 
    from matplotlib.widgets import Slider, Button, RadioButtons   
    button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
    ax2 = plt.axes([0.1, 0.325, 0.5, 0.05])

    ax2 = plt.axes([0, 0, 0.5, 0.05])
    ax2.patch.set_alpha(0.5) 
    Slider(ax2, 'Reset2', facecolor='red', alpha=0.5, valmin=0, valmax=10)
    ax2.patch.alpha = 0.5 
def escape_to_exit(event)
Expand source code Browse git
def escape_to_exit(event):
    if event.key == 'escape' or event.key == 'q' or event.key == 'ctrl+c':
        import vipy.globals            
        vipy.globals._user_hit_escape(True)
def figure(fignum=None)
Expand source code Browse git
def figure(fignum=None):
    if fignum is not None:
        plt.figure(fignum)
    else:
        plt.figure()
    return plt
def flush()
Expand source code Browse git
def flush():
    if matplotlib.get_backend().lower() == 'macosx':
        plt.draw()  # YUCK: to flush buffer on video play, will be slow
    plt.pause(0.001)
def frame(fr, im=None, color='b.', markersize=10, figure=None, caption=None)

Show a scatterplot of fr=[[x1,y1],[x2,y2]…] 2D points, all the same color

Expand source code Browse git
def frame(fr, im=None, color='b.', markersize=10, figure=None, caption=None):
    """Show a scatterplot of fr=[[x1,y1],[x2,y2]...] 2D points, all the same color"""
    if figure is not None:
        plt.figure(figure)
    else:
        plt.figure()
        plt.clf()

    plt.axes([0,0,1,1])
    plt.plot(fr[:,0],fr[:,1],color)
    plt.axis('off')
    plt.draw()
def imdetection(img, detlist, fignum=None, bboxcolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0, 0))

Show bounding boxes from a list of vipy.object.Detections on the same image, plotted in list order with optional captions

Expand source code Browse git
def imdetection(img, detlist, fignum=None, bboxcolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0)):
    """Show bounding boxes from a list of vipy.object.Detections on the same image, plotted in list order with optional captions """

    # Create image
    #fignum = imshow(img, fignum=fignum)  # this can fail on MacOSX, go the slow route instead
    fignum = _imshow_tight(img, fignum=fignum)     

    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html
    
    # Valid detections
    for (k,det) in enumerate(detlist):
        if do_caption and det.category() is not None and len(det.category()) != 0 and (det.category()[0:2] != '__'):  # prepending category with '__' will disable caption
            bboxcaption = det.category()
        else:
            bboxcaption = None

        if islist(bboxcolor):
            bboxcolor_ = bboxcolor[k]
        else:
            bboxcolor_ = bboxcolor

        if islist(textcolor):
            textcolor_ = textcolor[k]
        else:
            textcolor_ = textcolor

        boundingbox(img, xmin=det.xmin(), ymin=det.ymin(), xmax=det.xmax(), ymax=det.ymax(), bboxcaption=bboxcaption,
                    fignum=fignum, bboxcolor=bboxcolor_, facecolor=facecolor, facealpha=facealpha, textcolor=textcolor_, textfacecolor=textfacecolor, fontsize=fontsize, captionoffset=captionoffset, textfacealpha=textfacealpha)

    return fignum
def imflush()
Expand source code Browse git
def imflush():
    if matplotlib.get_backend().lower() == 'macosx':
        plt.draw()  # YUCK: to flush buffer on video play, will be slow
    plt.pause(0.001)

    # this is necessary for imshow only, maybe to trigger remove() of child?
    # However, this breaks jupyter notebook image show if we use gca()
    [a.annotate('', (0,0)) for i in plt.get_fignums() for a in plt.figure(i).axes]
    
    #plt.gca().annotate('', (0,0))  
def imframe(img, fr, color='b', markersize=10, label=None, figure=None)

Show a scatterplot of fr=[[x1,y1],[x2,y2]…] 2D points overlayed on an image, all the same color

Expand source code Browse git
def imframe(img, fr, color='b', markersize=10, label=None, figure=None):
    """Show a scatterplot of fr=[[x1,y1],[x2,y2]...] 2D points overlayed on an image, all the same color"""
    if figure is not None:
        fig = plt.figure(figure)
    else:
        fig = plt.figure()

    figure = plt.gcf().number

    plt.clf()

    ax = plt.Axes(fig, [0., 0., 1., 1.], frameon=False)
    fig.add_axes(ax)

    plt.axis('off')
    ax.set_axis_off()
    for a in plt.gcf().axes:
        a.get_xaxis().set_visible(False)
        a.get_yaxis().set_visible(False)

    plt.autoscale(tight=True)
    plt.plot(fr[:,0],fr[:,1],'%s.' % color, markersize=markersize, axes=ax)

    if label is not None:
        for ((x,y),lbl) in zip(fr, label):
            ax.text(x, y, lbl, color='white')

    return plt
def imkeypoints(img, kplist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0, 0))

Show Keypoint2d on the same image, plotted in list order with optional captions

Expand source code Browse git
def imkeypoints(img, kplist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0)):
    """Show `vipy.object.Keypoint2d` on the same image, plotted in list order with optional captions """

    # Create image
    fignum = imshow(img, fignum=fignum) 

    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html
    
    # Valid keypoints
    (x,y,size,color,caption,captioncolor) = ([],[],[],[],[],[])
    for (k,p) in enumerate(kplist):
        x.append(p.x)
        y.append(p.y)
        size.append(max(p.r, 1)**2)  # size is pts squared
        
        if do_caption and p.category() is not None and len(p.category()) != 0 and (p.category()[0:2] != '__'):  # prepending category with '__' will disable caption
            caption.append(p.category())
        else:
            caption.append(None)

        if islist(bordercolor):
            color.append(mcolors.to_hex(bordercolor[k]) + 'BB')  # with alpha
        else:
            color.append(mcolors.to_hex(bordercolor) + 'BB')  # with alpha
            
        if islist(textcolor):
            captioncolor.append(textcolor[k])
        else:
            captioncolor.append(textcolor)

    plt.figure(fignum)
    plt.scatter(x, y, c=color, s=size)
    return fignum
def imobjects(img, objlist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0, 0), kp_alpha=0.8)

Show Keypoint2d on the same image, plotted in list order with optional captions

Expand source code Browse git
def imobjects(img, objlist, fignum=None, bordercolor='green', do_caption=True, facecolor='white', facealpha=0.5, textcolor='green', textfacecolor='white', textfacealpha=1.0, fontsize=10, captionoffset=(0,0), kp_alpha=0.8):
    """Show `vipy.object.Keypoint2d` on the same image, plotted in list order with optional captions """

    # Create image
    fignum = imshow(img, fignum=fignum) 
    plt.gca().set_autoscale_on(False)


    # A better way? https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html

    # Valid detections
    detlist = [p for p in objlist if isinstance(p, vipy.object.Detection)]
    if len(detlist) > 0:
        for (k,det) in enumerate(detlist):
            if do_caption and det.category() is not None and len(det.category()) != 0 and (det.category()[0:2] != '__'):  # prepending category with '__' will disable caption
                bboxcaption = det.category()
            else:
                bboxcaption = None

            if islist(bordercolor):
                bboxcolor_ = bordercolor[k]
            else:
                bboxcolor_ = bordercolor

            if islist(textcolor):
                textcolor_ = textcolor[k]
            else:
                textcolor_ = textcolor

            boundingbox(img, xmin=det.xmin(), ymin=det.ymin(), xmax=det.xmax(), ymax=det.ymax(), bboxcaption=bboxcaption,
                        fignum=fignum, bboxcolor=bboxcolor_, facecolor=bboxcolor_, facealpha=facealpha, textcolor=textcolor_, textfacecolor=textfacecolor, fontsize=fontsize, captionoffset=captionoffset, textfacealpha=textfacealpha)
    
    # Valid keypoints
    kplist = [p for p in objlist if isinstance(p, vipy.object.Keypoint2d)]    
    plt.gca().set_autoscale_on(False)

    if len(kplist) > 0:
        (x,y,size,colors,r,patches) = ([],[],[],[],[],[])
        for (k,p) in enumerate(kplist):
            x.append(p.x)
            y.append(p.y)
            r.append(p.r)
            
            c = mcolors.to_hex(bordercolor[k] if islist(bordercolor) else bordercolor)
            colors.append(c + format(int(255*kp_alpha), '02X'))  # with alpha
            patches.append(Circle((p.x, p.y), p.r))

        plt.gca().add_collection(PatchCollection(patches, facecolors=colors, edgecolors='silver', clip_on=True, linewidths=0.5))  # scale radius with window size
    return fignum
def imshow(img, fignum=None)

Show an image in a figure window (optionally visible), reuse previous figure if it is the same shape

Expand source code Browse git
def imshow(img, fignum=None):
    """Show an image in a figure window (optionally visible), reuse previous figure if it is the same shape"""
    global FIGHANDLE

    if fignum in plt.get_fignums() and fignum in FIGHANDLE and FIGHANDLE[fignum].get_size() == img.shape[0:2]:
        # Do not delete and recreate the figure, just change the pixels 
        FIGHANDLE[fignum].set_data(img)

        # Delete all polygon and text overlays from previous drawing so that they can be overwritten on current frame
        for c in plt.gca().get_children():
            desc = c.__repr__()
            if 'Text' in desc or 'Polygon' in desc or 'Circle' in desc or 'Line' in desc or 'Patch' in desc or 'PathCollection' in desc:
                try:
                    c.remove()
                except:
                    # MacOSX fails with the error: NotImplementedError: cannot remove artist
                    # fallback on closing figure completely
                    (fignum, imh) = _imshow_tight(img, fignum=fignum) 
                    FIGHANDLE[fignum] = imh
                    return fignum

    else:
        # Jupyter notebook does not respect fignum.  It is always one when inspecting plt.get_fignums()
        if fignum in plt.get_fignums() and fignum in FIGHANDLE:
            close(fignum)
            #pass  # don't close unless user requests, this is faster, but window size does not change with image resolution
        (fignum, imh) = _imshow_tight(img, fignum=fignum)
        FIGHANDLE[fignum] = imh
    return fignum
def noshow(fignum)
Expand source code Browse git
def noshow(fignum):
    plt.ioff()
def savefig(filename=None, fignum=None, pad_inches=0, dpi=None, bbox_inches='tight', format=None)
Expand source code Browse git
def savefig(filename=None, fignum=None, pad_inches=0, dpi=None, bbox_inches='tight', format=None):
    if fignum is not None:
        plt.figure(fignum)
    if filename is None:
        filename = temppng()
    plt.savefig(filename, pad_inches=pad_inches, dpi=dpi, bbox_inches=bbox_inches, format=format)
    return filename
def show(fignum)
Expand source code Browse git
def show(fignum):
    fig = plt.figure(fignum) 
    fig.canvas.draw()    
    plt.ion()
    plt.show()
def text(caption, xmin, ymin, fignum=None, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, linewidth=3, facecolor='white', facealpha=0.5, alpha=1.0, pad=0.5, captionoffset=0)
Expand source code Browse git
def text(caption, xmin, ymin, fignum=None, textcolor='black', textfacecolor='white', textfacealpha=1.0, fontsize=10, linewidth=3, facecolor='white', facealpha=0.5, alpha=1.0, pad=0.5, captionoffset=0):
    plt.figure(fignum) if fignum is not None else plt.gcf()
    lw = linewidth  # pull in the boxes by linewidth so that they do not overhang the figure

    newlines = caption.count('\n')
    
    # MatplotlibDeprecationWarning: The 's' parameter of annotate() has been renamed 'text' since Matplotlib 3.3            
    handle = plt.annotate(**{'text' if matplotlib_version_at_least_3p3 else 's': caption},
                          alpha=alpha, xy=(xmin,ymin), xytext=(xmin, ymin+captionoffset), xycoords='data',
                          color=textcolor, bbox=None if textfacecolor is None else dict(facecolor=textfacecolor, edgecolor=None, alpha=textfacealpha, boxstyle='square', pad=pad),
                          fontsize=fontsize, clip_on=True)

    return fignum

Classes

class Annotate
Expand source code Browse git
class Annotate(object):
    def __init__(self):
        self.ax = plt.gca()
        self.rect = Rectangle((0,0), 1, 1)
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.ax.add_patch(self.rect)
        self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
        self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)

    def on_press(self, event):
        print ('press')
        self.x0 = event.xdata
        self.y0 = event.ydata

    def on_release(self, event):
        print ('release')
        self.x1 = event.xdata
        self.y1 = event.ydata
        self.rect.set_width(self.x1 - self.x0)
        self.rect.set_height(self.y1 - self.y0)
        self.rect.set_xy((self.x0, self.y0))
        self.ax.figure.canvas.draw()

Methods

def on_press(self, event)
Expand source code Browse git
def on_press(self, event):
    print ('press')
    self.x0 = event.xdata
    self.y0 = event.ydata
def on_release(self, event)
Expand source code Browse git
def on_release(self, event):
    print ('release')
    self.x1 = event.xdata
    self.y1 = event.ydata
    self.rect.set_width(self.x1 - self.x0)
    self.rect.set_height(self.y1 - self.y0)
    self.rect.set_xy((self.x0, self.y0))
    self.ax.figure.canvas.draw()
class DraggableRectangle (rect)
Expand source code Browse git
class DraggableRectangle(object):
    def __init__(self, rect):
        self.rect = rect
        self.press = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return

        contains, attrd = self.rect.contains(event)
        if not contains: return
        log.info('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if self.press is None: return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        #log.info('x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f' %
        #      (x0, xpress, event.xdata, dx, x0+dx))
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        self.rect.figure.canvas.draw()


    def on_release(self, event):
        'on release we reset the press data'
        self.press = None
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

        
    #a = Annotate()
    #plt.show()    

    #fig = plt.figure()
    #ax = fig.add_subplot(111)
    #rects = ax.bar(range(10), 20*np.random.rand(10))
    #drs = []
    #for rect in rects:
    #    dr = DraggableRectangle(rect)
    #    dr.connect()
    #    drs.append(dr)
    #
    #plt.show()

Methods

def connect(self)

connect to all the events we need

Expand source code Browse git
def connect(self):
    'connect to all the events we need'
    self.cidpress = self.rect.figure.canvas.mpl_connect(
        'button_press_event', self.on_press)
    self.cidrelease = self.rect.figure.canvas.mpl_connect(
        'button_release_event', self.on_release)
    self.cidmotion = self.rect.figure.canvas.mpl_connect(
        'motion_notify_event', self.on_motion)
def disconnect(self)

disconnect all the stored connection ids

Expand source code Browse git
def disconnect(self):
    'disconnect all the stored connection ids'
    self.rect.figure.canvas.mpl_disconnect(self.cidpress)
    self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
    self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
def on_motion(self, event)

on motion we will move the rect if the mouse is over us

Expand source code Browse git
def on_motion(self, event):
    'on motion we will move the rect if the mouse is over us'
    if self.press is None: return
    if event.inaxes != self.rect.axes: return
    x0, y0, xpress, ypress = self.press
    dx = event.xdata - xpress
    dy = event.ydata - ypress
    #log.info('x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f' %
    #      (x0, xpress, event.xdata, dx, x0+dx))
    self.rect.set_x(x0+dx)
    self.rect.set_y(y0+dy)

    self.rect.figure.canvas.draw()
def on_press(self, event)

on button press we will see if the mouse is over us and store some data

Expand source code Browse git
def on_press(self, event):
    'on button press we will see if the mouse is over us and store some data'
    if event.inaxes != self.rect.axes: return

    contains, attrd = self.rect.contains(event)
    if not contains: return
    log.info('event contains', self.rect.xy)
    x0, y0 = self.rect.xy
    self.press = x0, y0, event.xdata, event.ydata
def on_release(self, event)

on release we reset the press data

Expand source code Browse git
def on_release(self, event):
    'on release we reset the press data'
    self.press = None
    self.rect.figure.canvas.draw()
class DraggableRectangleFast (rect)
Expand source code Browse git
class DraggableRectangleFast(object):
    lock = None  # only one can be animated at a time
    def __init__(self, rect):
        self.rect = rect
        self.press = None
        self.background = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return
        if DraggableRectangle.lock is not None: return
        contains, attrd = self.rect.contains(event)
        if not contains: return
        log.info('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata
        DraggableRectangle.lock = self

        # draw everything but the selected rectangle and store the pixel buffer
        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        self.rect.set_animated(True)
        canvas.draw()
        self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

        # now redraw just the rectangle
        axes.draw_artist(self.rect)

        # and blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if DraggableRectangle.lock is not self:
            return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        # restore the background region
        canvas.restore_region(self.background)

        # redraw just the current rectangle
        axes.draw_artist(self.rect)

        # blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_release(self, event):
        'on release we reset the press data'
        if DraggableRectangle.lock is not self:
            return

        self.press = None
        DraggableRectangle.lock = None

        # turn off the rect animation property and reset the background
        self.rect.set_animated(False)
        self.background = None

        # redraw the full figure
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)    

Class variables

var lock

The type of the None singleton.

Methods

def connect(self)

connect to all the events we need

Expand source code Browse git
def connect(self):
    'connect to all the events we need'
    self.cidpress = self.rect.figure.canvas.mpl_connect(
        'button_press_event', self.on_press)
    self.cidrelease = self.rect.figure.canvas.mpl_connect(
        'button_release_event', self.on_release)
    self.cidmotion = self.rect.figure.canvas.mpl_connect(
        'motion_notify_event', self.on_motion)
def disconnect(self)

disconnect all the stored connection ids

Expand source code Browse git
def disconnect(self):
    'disconnect all the stored connection ids'
    self.rect.figure.canvas.mpl_disconnect(self.cidpress)
    self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
    self.rect.figure.canvas.mpl_disconnect(self.cidmotion)    
def on_motion(self, event)

on motion we will move the rect if the mouse is over us

Expand source code Browse git
def on_motion(self, event):
    'on motion we will move the rect if the mouse is over us'
    if DraggableRectangle.lock is not self:
        return
    if event.inaxes != self.rect.axes: return
    x0, y0, xpress, ypress = self.press
    dx = event.xdata - xpress
    dy = event.ydata - ypress
    self.rect.set_x(x0+dx)
    self.rect.set_y(y0+dy)

    canvas = self.rect.figure.canvas
    axes = self.rect.axes
    # restore the background region
    canvas.restore_region(self.background)

    # redraw just the current rectangle
    axes.draw_artist(self.rect)

    # blit just the redrawn area
    canvas.blit(axes.bbox)
def on_press(self, event)

on button press we will see if the mouse is over us and store some data

Expand source code Browse git
def on_press(self, event):
    'on button press we will see if the mouse is over us and store some data'
    if event.inaxes != self.rect.axes: return
    if DraggableRectangle.lock is not None: return
    contains, attrd = self.rect.contains(event)
    if not contains: return
    log.info('event contains', self.rect.xy)
    x0, y0 = self.rect.xy
    self.press = x0, y0, event.xdata, event.ydata
    DraggableRectangle.lock = self

    # draw everything but the selected rectangle and store the pixel buffer
    canvas = self.rect.figure.canvas
    axes = self.rect.axes
    self.rect.set_animated(True)
    canvas.draw()
    self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

    # now redraw just the rectangle
    axes.draw_artist(self.rect)

    # and blit just the redrawn area
    canvas.blit(axes.bbox)
def on_release(self, event)

on release we reset the press data

Expand source code Browse git
def on_release(self, event):
    'on release we reset the press data'
    if DraggableRectangle.lock is not self:
        return

    self.press = None
    DraggableRectangle.lock = None

    # turn off the rect animation property and reset the background
    self.rect.set_animated(False)
    self.background = None

    # redraw the full figure
    self.rect.figure.canvas.draw()